Chinaunix

标题: linux git常见命令整理 [打印本页]

作者: compare2000    时间: 2014-04-22 15:34
标题: linux git常见命令整理
git常见命令整理  
【基本概念】
A) git本地由三个部分组成:工作区 和 暂存区 和 本地库。
B) .git目录才是仓库的灵魂,.git目录里面包括 暂存区 和 本地库,.git目录外的地方是工作区。
C) git中包含4类对象 (commit 提交对象, tree 目录, blob 文件, tag 标记)


【git模型】
工作区 = working directory
暂存区 = snapshot/stage/index
本地库 = local repo




【.git目录】
HEAD 指向当前分支
config 项目配置文件
description 供GitWeb程序使用
hooks/ 客户端与服务端钩子脚本
info/ 忽略模式
index 暂存区域信息
objects/ 所有数据内容
refs/ 指向所有commit的指针




【git对象】
git提交便产生一个commit对象,commit对象中包含一个tree对象,tree对象中又会包含其他的tree对象或是blob对象,blob对象是最小的组成单元,即独立的文件。
每个对象都对应一个唯一的SHA-1值,只有当文件或目录有修改时这个值才会重新计算发生改变。
$ git log 可以查看所有commit对象
$ git ls-tree <commit> 查看commit对象中的tree对象
$ git show <blob> 查看blob的具体内容


【参数解释】
HEAD = HEAD~0
HEAD^ = HEAD~1
HEAD^^ = HEAD~2




【git 命令】
git init                                  // 如果仅仅在本地创建空配置库,不需要带任何参数
git init --bare                                 // 在服务器上创建一个新的空配置库,比如是在repositories/prj/Code.git目录下执行。bare repository主要是用作分享版本库。
git clone git@YOUR_SERVER:prj/Code.git    // 从远程服务器的 repositories/prj/Code.git 目录下克隆
git push                                  // 上传所有内容,从 本地库 到 服务器
git push origin master                    // 上传master分支
git push origin --tags                    // 上传本地标签
git fetch                                 // 从服务器上下载更新代码到 本地库,工作区和 暂存区 不会被改变。
git pull = git fetch + git checkout       // 下载数据到 本地库,并更新到 暂存区 和工作区
git remote


git add
git rm
git commit
git diff
git diff --cached
git checkout
git status
git show
git log




git reset
git revert


git branch
git merge
git show-branch


git config
git tag
git stash


.gitignore  需要忽略的临时文件
【举例】
*.o
obj/
.svn/




【git remote】-- 远程操作命令
git remote -v              // 显示远程库的路径
git remote add             // git remote add <name> <url>
git remote rm   
git remote show  


【git add】-- 如果你新增了文件或文件夹,那么就要老老实实的先git add .  再git commit
git add file1 file2 file3  // 将新增加文件加入到 暂存区 中
git add .                  // 所有文件全部加入到 暂存区 中,包括子目录,不包括空目录
git add -i                 // 进入交互式add,尚未体验,有空试一下


【git rm】-- git 提供相应命令 (add,rm) 将工作区变更加入到 暂存区
git rm -rf xxx
git rm xxx.file




【git commit】-- 将 暂存区 的东西加入到 本地库,(注意:这里只是提交到本地库,还需要通过 push 命令才能将本地库的内容上传到服务器)
git commit -m “something” someFile //提交指定文件
git commit -m "写上描述"           // 最常用的用法
git commit -a -m "写上描述"        // -a 参数请谨慎处理,常见的错误结果是把某些编译中间文件也加入配置库。
commit和commit -a的区别
commit -a相当于:
第一步:自动地add所有改动的代码,使得所有的开发代码都列于index file中
第二步:自动地删除那些在index file中但不在工作树中的文件
第三步:执行commit命令来提交




【git diff】
git diff SHA_AAA           // 比较工作目录和SHA_AAA之间的差异
git diff SHA_AAA..SHA_BBB  // 比较两次不同的commit之间的差异
git diff                   // 比较工作区和 暂存区 的差异。 这个命令只在git add之前使用有效,如果已经add了,那么此命令输出为空。
git diff --cached          // 比较 暂存区 和HEAD的差异。 在git add之后在git commit之前有效,如果已经commit了,那么此命令输出为空。
git diff --name-only       // 只列出不同文件的名字,而不显示具体的差异之处
git diff -stat             // 可以统计数据,比较特别的命令


【git checkout】 -- 恢复 工作区 的内容
git checkout -- test.txt   // 从 暂存区 恢复到 工作区,相当于撤销工作区中 test.txt 文件尚未提交的修改
git checkout -f            // 从 本地库 恢复到 暂存区和工作区, 强制修改工作区的内容,一般对于当前修改不满意,放弃修改后从头开始
git checkout HEAD test.txt // 从 本地库 恢复到 暂存区和工作区, 取HEAD中的这个文件test.txt
git checkout HEAD .        // 从 本地库 恢复到 暂存区和工作区, 取HEAD
git checkout $(git rev-list master -n 1 --first-parent --before=2012-03-21)
// 取master分支,2012-03-21 00:00:00 的代码, 这个命令可以帮助取没打标签的版本代码,一般对应每日构建的版本。


git checkout SHA_value     // 从 本地库 恢复到 暂存区和工作区, 取特定的commit id=SHA_value




【git status】-- 查看变化,变化包括工作区和暂存区的差异,暂存区和本地库的差异,本地库的提交记录,分支/合并的状态




【git show】
git show SHA_value         // 显示特定commit的内容
【git log】
git log --name-only        // 仅在提交信息后显示已修改的文件清单
git log --name-status      // 显示文件名以及状态(新增、修改、删除)
git log -p                 // 会输出非常详细的日志内容,包括了每次git commit之后都做了哪些源码的修改,可以看到具体的差异。
git log -2                 // 最近两次提交的日志。 -(n) 仅显示最近的n 条提交
git log --graph            // 图形化方式呈现提交记录, ASCII 图形表示
git log --stat             // 显示修改文件名以及修改统计信息(文件大小变化)
git log --raw -5           // 文件列表,文件属性
git log --grep=mouse       // 按照关键字搜索
git log --relative-date    // 使用较短的相对时间显示(比如,“2 weeks ago”)。


git log --pretty="%h:%s" --author=gke --since="2011-10-01" --before="2011-10-05" --no-merges -- t/
// 2011年10月期间,gke 提交的但未合并的测试脚本(位于项目的t/ 目录下的文件)


        --since, --after       // 仅显示指定时间之后的提交。
        --until, --before      // 仅显示指定时间之前的提交。
git log --author=g00196971 --after="2011-12-25" > 2011_12_25_gke.txt               // 查看 2011-12-25之后g00196971 的提交记录
git log --after="2011-12-09" --before="2011-12-11" > git_log_1209_1211.txt         // 查看 2011-12-09到2011-12-11 时间段内的提交记录
git log --since="1 day"     // 可以给出各种时间格式,比如说具体的某一天(“2012-01-15”),或者是多久以前(“2 years 1 day 3 minutes ago”)。
git log --pretty=oneline    // 提交记录格式 显示为一行。 可用的选项包括oneline,short,full,fuller 和format(后跟指定格式)。
git log 目录名              // 查看特定的目录的提交记录


【去除别人分支merge过来的提交记录】
git log --pretty=oneline --abbrev-commit master > master.log
git log --pretty=oneline --abbrev-commit > all.log
grep -vwFf master.log all.log > my_branch.log


【git revert和git reset的区别】
git reset   // 是撤销某次提交,但是此次之后的修改都会被退回到暂存区
git revert  // 是撤销 某一次/某中间几次 的操作,此次操作之前的commit都会被保留


1. git revert是用一次新的commit来回滚之前的commit,git reset是直接删除指定的commit。
2. 在回滚这一操作上看,效果差不多。但是在日后继续merge以前的老版本时有区别。
   因为git revert是用一次逆向的commit“中和”之前的提交,因此日后合并老的branch时,导致这部分改变不会再次出现,但是git reset是之间把某些commit在某个branch上删除,
   因而和老的branch再次merge时,这些被回滚的commit应该还会被引入。
3. git reset 是把HEAD向后移动了一下,而git revert是HEAD继续前进,只是新的commit的内容和要revert的内容正好相反,能够抵消要被revert的内容。




【git reset】
git reset --hard SHA_value   // 销毁自己的修改, SHA_value可以从git log中查找
git reset --hard HEAD~3      // 最后三个commit(即HEAD, HEAD^和HEAD~2)提交有问题,你想永久删除这三个commit。
git reset –-mixed            // --mixed可以省略,这是默认模式。
git reset --hard HEAD


A). --soft:  暂存区 和工作区中的内容不作任何改变,仅仅把HEAD指向<commit>。这个模式下可以在不改动当前工作环境情况下,改变head指针,即切到任意commit。
B). --mixed: 这是reset的default模式。 暂存区 中的内容会变化,但是工作区中的内容不变。
C). --hard:  暂存区 和工作区中的内容都会发生改变,就像刚clone出来一样,用来做还原操作且不想保存任何修改。




【git revert】
git revert HEAD       // 撤销前一次 commit
git revert HEAD^      // 撤销前前一次 commit
git revert SHA_value  // 撤销指定的版本,撤销也会作为一次提交进行保存。


A) git revert HEAD~3:丢弃最近的三个commit,把状态恢复到最近的第四个commit,并且提交一个新的commit来记录这次改变。
B) git revert -n master~5..master~2:丢弃从最近的第五个commit(包含)到第二个(不包含),但是不自动生成commit,这个revert仅仅修改working tree和 暂存区 。






【git branch】
git branch test_branch       // 创建一个实验分支
git branch                   // 查看总共有哪些分支
git branch -r                // 参看本地分支和远程分支的映射关系
git push origin test_branch  // 把本地当前分支的改动推到服务器的新分支上, 如果 origin test_branch 不加的话,那么会默认推送本地的所有的branch
git branch -d test_branch    // 合并成功之后,可以删除实验分支了。这个动作要谨慎,确定已经合并成功,不再需要这个分支了。
git checkout [branch_name]   // 切换到实验分支
git_all.sh checkout -b test_branch origin/test_branch   // git checkout -b [分支名] [远程名]/[分支名]




【git merge】
git merge test_branch        // 从test_branch分支合并到当前分支。
                             // 如果两个分支中有相同的代码被改动,会提示冲突,需要手动修改。手动修改当前分支中的冲突内容,然后再次merge实验分支到当前分支。


【git show-branch】
git show-branch -a          // 显示本地分支
git show-branch -r          // 显示远程分支




【git config】-- 配置和查询 git 的基本信息
git config --global user.name "g00196971"
git config --global user.email guanke@huawei.com
git config --global color.ui true
git config --global core.autocrlf input
git config --global merge.tool vimdiff
git config --list            // 检查已有的配置信息


【git tag】
git tag                                           // 查询所有标签
git tag -a REL_B011 -m 'software version = B011'  // 在本地打标签
git push origin --tags                            // 一次把本地打的标签全部上传
git tag -d REL_B011                               // 删除标签
git show tag                                      // 显示tag 的详细信息,包括标签的详细描述,对应的commit SHA_value
git checkout [tagname]                            // 和切换分支一样,可以切换到标签




【git stash】-- 使用场合:当前工作区内容已被修改,但是并未完成。这时Boss来了,说前面的分支上面有一个Bug,需要立即修复。可是我又不想提交目前的修改,因为修改没有完成。
                但是,不提交的话,又没有办法checkout到前面的分支。此时用Git Stash就相当于备份工作区了。然后在Checkout过去修改,就能够达到保存当前工作区,并及时恢复的作用。
git stash:                // 备份当前的工作区的内容,从最近的一次提交中读取相关内容,让工作区保证和上次提交的内容一致。同时,将当前的工作区内容保存到Git栈中。
git stash pop:            // 从Git栈中读取最近一次保存的内容,恢复工作区的相关内容。由于可能存在多个Stash的内容,所以用栈来管理,pop会从最近的一个stash中读取内容并恢复。
git stash list:           // 显示Git栈内的所有备份,可以利用这个列表来决定从那个地方恢复。
git stash clear:          // 清空Git栈。此时使用gitg等图形化工具会发现,原来stash的哪些节点都消失了。
git stash apply 版本号 :  // 取出特定的版本号对应的工作空间






【参数解释】
【git回车换行问题】


用git status命令查看的时候,一些没有更改过的文件也显示为modified。checkout到某个分支,什么都不做直接git status也会出现。
按理说能checkout到别的分支去说明现在这个分支已经是干净的了(实际上也是做过提交的),再跳回来的时候未做任何改动就会出现一堆已经修改过的文件。
用diff查看文件修改都是大段大段的修改,实际上代码都一样。不得已只好暂时commit上去。发现原来是行结束符惹的祸。
格式化是许多开发人员在协作时,特别是在跨平台情况下,遇到的令人头疼的细小问题。
由于编辑器的不同或者Windows程序员在跨平台项目中的文件行尾加入了回车换行符, 一些细微的空格变化会不经意地进入大家合作的工作或提交的补丁中。


熟悉 git的配置选项就可以解决这些问题。
在Windows上是CRLF来作为一行的结束符,而Linux上则是LF作为行结束符
在git中提供了autocrlf的设置,可以用来自动转换CRLF,它可以设置成true,false,input


$ git config --global core.autocrlf true
设置为true,在checkin时,行结束符被转换为LF。在checkout时,行结束符被转换为CRLF。


$ git config --global core.autocrlf input
设置为input,在checkin时,行结束符被转换为LF,但是checkout时,则不会转换。


$ git config --global core.autocrlf false
设置为false,无论checkin还是checkout时,都取消自动转换功能。




【文档】
git_community_book.pdf
pro_git_中文版本.pdf


【工具】
Git-1.7.9-preview20120201.exe
TortoiseGit-1.7.7.0-32bit.msi


【一些好网站】
Why Git is Better than X
http://thkoch2001.github.io/whygitisbetter/



A successful Git branching model

http://nvie.com/posts/a-successful-git-branching-model/



可视化方式学习git

http://pcottle.github.io/learnGitBranching/



【附件】
一些比较好的示意图

repo的用法(zz)





注:repo只是google用Python脚本写的调用git的一个脚本,主要是用来下载、管理Android项目的软件仓库。(也就是说,他是用来管理给git管理的一个个仓库的)

下载 repo 的地址: http://android.git.kernel.org/repo ,可以用以下二者之一来下载 repo

wget http://android.git.kernel.org/repo

或者

curl http://android.git.kernel.org/repo > ~/bin/repo  

下载完成后须修改repo的权限: chmod a+x ~/bin/repo



用repo sync 在抓去 android source code 的时候,会经常出现一些错误导致 repo sync 中断,每次都要手动开始。 可以用如下的命令,来自动重复

$?=1;

while [ $? -ne 0 ] ;

do  repo sync ;

done



获取帮助:

repo help [ command ]   //显示command 的详细的帮助信息内容

示例: repo help init 来获取 repo init 的其他用法



repo init -u URL 用以在当前目录安装 repository ,会在当前目录创建一个目录 ".repo"  -u 参数指定一个URL, 从这个URL 中取得repository 的 manifest 文件。   

示例:repo init -u git://android.git.kernel.org/platform/manifest.git

获取的manifest文件放在.repo目录中。命名为manifest.xml。这个文件的内容其实就是所有被git管理的仓库的列表!



可以用 -m 参数来选择获取 repository 中的某一个特定的 manifest 文件,如果不具体指定,那么表示为默认的 namifest 文件 (default.xml)

repo init -u git://android.git.kernel.org/platform/manifest.git -m dalvik-plus.xml

(有诸多供我们选择的manifest文件,所有的manifest文件都放在目录.repo/manifests中,该目录本身亦被git所管理,你可以cd进去看看)



可以用 -b 参数来指定某个manifest 分支。

repo init -u git://android.git.kernel.org/platform/manifest.git -b release-1.0

你会发现.repo/manifests是个被git管理的仓库,这里放的是所有的manifest文件(*.xml),因为被git管理,固然有分支,-b可以切换到你想要的分支然后再下载相关的xml文件,当然具体下载那个xml还要看-m参数了,所以如果你仅仅指定-b而没有-m的话,就是下载-b指定分支下的default.xml文件

如果不指定-b参数,那么会默认使用master分支



4. repo sync [project-list]

下载最新本地工作文件,更新成功,这本地文件和repository 中的代码是一样的。 可以指定需要更新的project , 如果不指定任何参数,会同步整个所有的项目。

如果是第一次运行 repo sync , 则这个命令相当于 git clone ,会把 repository 中的所有内容都拷贝到本地。 如果不是第一次运行 repo sync , 则相当于 git remote update ;  git rebase origin/branch .  repo sync 会更新 .repo 下面的文件。 如果在merge 的过程中出现冲突, 这需要手动运行  git  rebase --continue



5. repo update[ project-list ]

上传修改的代码 ,如果你本地的代码有所修改,那么在运行 repo sync 的时候,会提示你上传修改的代码,所有修改的代码分支会上传到 Gerrit (基于web 的代码review 系统), Gerrit 受到上传的代码,会转换为一个个变更,从而可以让人们来review 修改的代码。



6. repo diff [ project-list ]

        显示提交的代码和当前工作目录代码之间的差异。



7. repo download  target revision

        下载特定的修改版本到本地, 例如:  repo download pltform/frameworks/base 1241 下载修改版本为 1241 的代码



8. repo start newbranchname .

        创建新的branch分支。 "." 代表当前工作的branch 分支。



9.  repo prune [project list]

        删除已经merge 的 project



10. repo foreach [ project-lists] -c command

       对每一个 project 运行 command 命令



12. repo forall -c

这个命令会遍历所有的git仓库,并在每个仓库执行-c所指定的命令(这个被执行的命令就不限于仅仅是git命令了,而是任何被系统支持的命令,比如:ls 、 pwd 、cp 等等的 )

当我想通过这个命令遍历所有的仓库并在每个仓库执行"git checkout . "用以将每个仓库的改动都清除的时候,我这么输入命令:

repo forall -c git checkout .

我发现这样根本不行。看来repo不能遍历执行checkout这个命令。今天我终于想到了另外一个命令"git reset --hard HEAD" 哈哈

repo forall -c git reset --hard HEAD


再说一个新发现:以前用repo forall 执行一些命令的时候,可能再遍历到某个仓库的时候出了问题,但是我却苦于不知道这个仓库到底是哪个!一直也没有解决。今天终于找到了。。。。  关键时候还是要看命令自己带的帮助手册呀。。。

repo help forall  用这个命令查看下针对forall的帮助吧。说的很清楚,repo执行的时候加上-p参数就可以在遍历到每个仓库的时候先打印出当前的pwd,然后再继续执行-c所指定的命令。举例如下:


repo forall -p -c git branch   



//该命令会遍历所有仓库并打印每个仓库的分支情况,由于有了-p参数,这样便会打印出每个仓库的路径!!!



11. repo status

       显示 project 中每个仓库的状态,并打印仓库名称。



201111071237 更新

yasin.lee.x

———————————————————————————————————————————————————————
cpp@cpp:~$ repo
Traceback (most recent call last):
  File "/home/cpp/bin/repo", line 91, in ?
    import readline
ImportError: No module named readline
cpp@cpp:~$
———————————————————————————————————————————————————————
系统里明明有readline,可是repo脚本却无法导入,后来查到应该是python安装导致的错误,应该在python编译时加上关于readline的编译选项,应该按照如下操作安装python
首先安装readline软件包:
sudo apt-get install libreadline5-dev
sudo apt-get install zlib1g-dev
然后下载python源码进入python源码目录,编译并安装python:
1. make distclean
2. ./configure  --enable-readline  BASECFLAGS=-U_FORTIFY_SOURCE
3. make -j4
4. sudo make install
注意,我同时发现在python-2.4.3版本是支持该编译选项的,而在3.1.3版本中是不能识别这个--enable-readline编译参数的。所以我目前使用2.4.3版本
repo的命令集学习

repo子命令实际上是对GIT命令的封装。所有的repo命令都可以通过repo help <command>来获取帮助



1、repo init

repo init主要完成检出清单版本库,以及配置git用户的用户名和邮件地址的工作

如repo init -u ssh://android.huawei.com/platform/manifest.git -b hw/qcom/platform --no-repo-verify --repo-branch=stable

2、repo sync

repo sync用于参照清单文件克隆或同步版本库,相当于执行git clone,如果项目版本库已存在,则相当于执行了如下的二个命令:git remote update和git rebase origin/branch。

3、repo start

repo start实际上是对git checkout -b命令的封装,创建的分支应用到所有的项目库时,需使用--all参数。

如repo start hw/qcom/platform -all

4、repo status

repo status实际上是对git diff-index、git diff-files命令的封装,同时显示暂存区和本地修改文件的差异。

如:repo status repo/ti

5、repo checkout

repo checkout是对git checkout命令的封装,检出之间由repo start创建的分支。

如:repo checkout  hw/qcom/platform  repo/ti

6、repo branches

用于读取所有项目中的分支列表并汇总显示,该命令实际上通过读取.git/refs目录下的引用来获取分支列表,以及分支的发布状态信息等

如:repo branches repo/ti

7、repo diff

repo diff是对git diff的封装,用于分别显示各个项目工作区下的文件差异

8、repo stage

repo stage实际是对git add --interactive命令的封装,用于挑选各个项目工作区中的改动以加入暂存区中

如:reop stage -i repo/ti

9、repo upload

repo upload相当于git push,但repo upload不是将版本库的改动推送到克隆时的远程服务器,而是推送到代码审查服务器gerrit中,推送的数据传输使用的是SSH协议。

10、repo download

repo ownload用户代码的审核者下载和评估提交过来的修改文件内容,即上述通过repo upload过来的内容。

11、repo rebase

repo rebase是对git rebase命令的封装,该命令的参数加上-i时,只对当前的这个项目才生效。

12、repo prune

repo prune是对git branch -d命令的封装,该命令用于扫描项目的各个分支,并删除已经合并的分支。

13、repo forall

迭代器,可以对repo管理的项目进行迭代,可以将本机中所有的仓都一并执行相关git xxx的命令。

repo forall -c "git commit -m "commit new feature into project A""

作者: compare2000    时间: 2014-04-24 10:14
CI引入git仓库存储测试用例和测试框架。多个git仓库通过repo来管理。
注: 所有开发工作请在本地分支执行,需要提交的时候请先切换分支同步主干代码

要求:修改完一个问题单就将该问题单提交到本地,然后上传代码,具体操作请按照下面提到的代码操作流程。
[]:表示可选参数,最好带有参数,逐仓使用
projectname;git库中.git/config文件中remote标签里包含的peojectname值

经典命令分不同场景收集如下   
  [ 回目录 ]一.             下载同步代码   
1.       用repo初始化所有代码:

repo init -u ssh://shgit/git/android/platform/manifest.git -b branch_name --repo-url ssh://shgit/git/android/tools/repo.git

2.       用git克隆代码仓库

git clone git://…  (ssh:// shgit.marvell.com/git/android/…)

3.       用repo同步某个project

repo sync ./  = git fetch shgit + git rebase shgit/mrvl-gingerbread

4.       获取更新代码(尽量少用,因为是merge):

git pull = git fetch shgit + git merge shgit/mrvl-gingerbread

5.       衍合(尽量使用rebase)

git rebase shgit/mrvl-gingerbread

6.       当需要手动rebase或merge时:

git mergetool  (一般使用kdiff)

7.       挑拣(选择需要的commit放到你的branch上):

git cherry-pick sha

  [ 回目录 ]   
  [ 回目录 ]二.             检查代码库   
1.       检查代码状态:

git status

2.       检查变动:

git diff <commit1> <commit2>

3.       查看Log:

git log

4.       查看具体文件:

git blame file

5.       查看分支信息:

git branch  git branch -a

6.       查看远程git信息:

git remote show shgit

7.       显示tag信息, 打tag:

git tag -l  git tag foo



  [ 回目录 ]三.             代码提交或撤销   
1.       提交: -a 跳过暂存区;--amend 合并前次提交

git commit -s -m “messge” -a --amend

2.       Push到仓库:

git push ssh_server your_branch[:branch_on_ssh_server]

git push ssh://yjque@shgit.marvell.com:29418/project-name.git HEAD:refs/for/branch-name

3.       增加,移动,删除文件

git add file; git mv oldfile new file; git rm file

4.       用当前暂存区取消当前最后一次提交

git commit --amend

5.       取消暂存区文件

git reset HEAD <file> --hard  (HEAD^ HEAD^^… or commit sha)

6.       Revert某个提交

git revert commit_sha



  [ 回目录 ]四.             管理branch   
1.       创建branch

git branch target_branch  src_branch

2.       删除branch

git branch -D target_branch

3.       切换branch

git checkout target_branch

4.       创建并切换到branch

git checkout -b new src_branch

5.       合并branch(少用)

git merge src_branch



  [ 回目录 ]五.       管理Patch   
1.       格式化生成PatchA:

git format-patch START_COMMIT -n (from commit no. )

git format-patch -n  (from latest)

2.       用AM打PatchA:

git am patchA

3.       用diff生成patch:

git diff > patchB

4.       打用diff生成的Patch:

git apply patchB






--------------------------------------------------------------------------------


  [ 回目录 ]常用git命令   
•同步主干代码:    repo sync [projectname]
•创建本地分支:    git checkout -b branchname
•切换分支:        git checkout branchname
•将主干分支更新合并到本地开发: git rebase remote branchname
•添加修改到缓存:  git add [-A] [filepath...]
•提交修改到本地库:git commit
•补充提交到最新的修改: git commit --amend
•撤销修改:        git checkout filepath
•删除增加:        git rm filepath      请谨慎使用,除非迫不得已,不建议使用
•查看提交记录:    git log [--oneline]
•将本地提交上传到主干: repo upload [projectname]


--------------------------------------------------------------------------------



  [ 回目录 ]正确的代码操作流程   



  [ 回目录 ]获得最新代码:   
git checkout remote
repo sync
git rebase remote branchname
if(冲突){
解决冲突
git add
git rebase --continue

}



--------------------------------------------------------------------------------



  [ 回目录 ]进行开发:   
代码开发
git add

git commit



--------------------------------------------------------------------------------



  [ 回目录 ]上传提交:   
git checkout remote
repo sync
git rebase remote branchname
if(冲突){
解决冲突
git add
git rebase --continue
}
git rebase branchname remote

repo upload projectname



--------------------------------------------------------------------------------
  [ 回目录 ] 服务器版本下载:   
repo init -u git@192.168.1.11:i700t_60501010/platform/manifest.git -b froyo_almond -m M76XXTSNCJNLYA60501010.xml

repo sync

repo forall -c git checkout --track origin/i700t_60501010 -b [你的本地分支]

或者:

git clone git@192.168.1.11:6120_gingerbread/Arm9-6120.git

git checkout --track origin/arm9_6120 -b [你的本地分支名]



  [ 回目录 ]服务器新加仓库同步:   
请按如下步骤取得Lanucher_wpon的代码:

1:~> cd I700T/.repo/manifest

2: I700T/.repo/manifest> git pull --rebase

3: I700T/.repo/manifest> cd ../..

4: I700T> repo sync platform/packages/apps/Launcher_wpon

5:I700T> cd packages/apps/Launcher_wpon

6:I700T/packages/apps/Launcher_wpon>

7:git checkout --track origin/froyo_almond -b [你的本地分支]



  [ 回目录 ]上传本地修改到服务器   
repo forall -c git pull --rebase        和服务器同步(要上传代码前,一般先进行此操作)

git add .    或git add 文件名            添加当前仓库修改的文件

git commit -m "..."                       在引号中添加你的修改记录

git push origin  本地分支名:froyo_almond       上传本地修改的代码




--------------------------------------------------------------------------------


  [ 回目录 ] 设置一些默认的全局变量,对所有工程代码有效   
git config --global user.name yourmail

git config --global user.email yourmail

git config --global push.default tracking     这样后续git push 后面不用带参数



  [ 回目录 ]查看修改记录   
git log                       本地仓库修改记录

repo forall -c git log --since="2011-04-19" --until="2011-04-21"   按条件查看工程所有仓库修改记录

repo status                查看工程中所有仓库的修改状态(包括文件位置)

git status                   查看仓库修改状态



  [ 回目录 ]分支相关   
git branch                 查看本地branch

git branch -r              查看远程branch

git branch -a              查看所有branch

git branch -D  (-d)  (branch name)    删除branch

cat .git/config      可以查看本地branch一些信息



  [ 回目录 ] 修改恢复相关   
git checkout filename1  filename2  ...           取消本地修改,和服务器同步

git stash  

git stash apply          先stash本地修改,然后执行git pull --rebase同步,最后再APPLY恢复自己的修改

git reset --soft head_commit  恢复到最后一次commit,保持代码修改

git reset --hard commit    恢复到指定一次commit,放弃之前所有修改

#回退a.py这个文件的版本到上一个版本  

git reset HEAD^ a.py

git reset commitNO filename



  [ 回目录 ]本地某仓库出问题了,不好闹腾时,删除之,并重新同步跟踪   
project_folder/vendor/qcom$ rm -rf proprietary/                         进到相应目录,删除之

project_folde$ repo sync platform/vendor/qcom/proprietary       重新repo sync,后面路径名称可查看:

                                                                                                 gedit .repo/manifest.xml

git branch -a    ----列举所有BRANCH

git branch -D 700_arm11_server

git branch -D 700_arm11_server_wifi  --删掉所有本地branch

git checkout --track origin/froyo_almond -b 700_arm11_server   然后track远程branch,重新创建本地分支



  [ 回目录 ]tag的使用   
git tag [tag_name] [version],在对应版本上(一般用change的SHA1),创建tag

git tag -l 列出当前tag

git tag -d [tag_name] 删除tag

有了tag以后,可以使用git checkout [tag_name] -b [branch_name]来检出对应tag时刻的代码。也可以用tag name来实现diff等功能。



  [ 回目录 ] patch的使用   
git diff filename1 filename2 ...                  修改位置对比,查看源码

git diff > xxx.patch                                  将修改的地方打成一个patch

git apply xxx.patch                                  将patch打上



  [ 回目录 ]后续有用到的命令继续添加   
git revert 是撤销某次提交。git reset –hard,才是退回到以前的版本

git reset --soft commitNum      保存代码修改的reset,但这个时候无法使用git diff 进行比较修改的文件,必须:

git reset filename filename     这样就可以git diff查看

git diff ffd98b291e0caa6c33575c1ef465eae661ce40c9 b8e7b00c02b95b320f14b625663fdecf2d63e74c 查看某两个版本之间的差异

git diff ffd98b291e0caa6c33575c1ef465eae661ce40c9:filename b8e7b00c02b95b320f14b625663fdecf2d63e74c:filename 查看某两个版本的某个文件之间的差异

  Git 命令别名

$ git config –global alias.co checkout // co将会成为checkout的别名

$ git config –global alias.br branch

$ git config –global alias.ci commit

$ git config –global alias.st status

$ git config –global user.name “username”

$ git config –global user.email username@mail.com



  [ 回目录 ] gitk使用   
gitk & 可以让终端继续编写执行命令

gitk  文件名     可以查看某个文件的修改记录



--------------------------------------------------------------------------------










--------------------------------------------------------------------------------

  [ 回目录 ]参考资料:   

1:http://blog.csdn.net/big_ant/article/details/6338990

2:http://blog.csdn.net/snowgeneral/article/details/6886933



作者: compare2000    时间: 2014-04-29 10:50
本帖最后由 compare2000 于 2014-05-05 15:12 编辑

如何在C程序中调用dll连接库里面的函数

#define DllImport    __declspec( dllexport )
#else
#define DllImport    __declspec( dllimport )
#endif
#define DllExport    __declspec( dllexport )
#else
#define DllImport
#define DllExport
#endif

#if defined(__STDC__) || defined(WIN32) || defined (hpux)
#define dsproto(x) x
#else
#define dsproto(x) ()
#endif

DllImport int DSServerMessage(const char *MsgIdStr, const char *DefMsg, const char *Prms[10], char *pMessage, int SizeMessage);
DllImport int DSCloseJob dsproto((DSJOB));



删所有文件
#!/bin/bash
count=0
SCANFILE()
{
local dir
local FILES
dir=$1
cd ${dir}
for FILES in `ls -l | awk '{print $8}'`
do

#屏蔽掉当前目录和上一级目录以及.svn,便于遍历#
if [ "." = "${FILES}" -o ".." = "${FILES}" -o ".svn" = "${FILES}" ]
then
  echo "the file is ${FILES}"
  continue
fi
#如果是目录进入并且遍历E
if [ -d ${FILES} ]
then
  pwd
  SCANFILE ${FILES}
  continue
fi

##前面排除掉目录,剩下的就是文件,不能使用-f来判断是否是常规文件,因为有可能是link文件
echo "`pwd`/${FILES}"
rm -rf ${FILES}
done
#返回上一级目录,因为FILES只是一文件名,不是一个绝对路径,所以需要返回上一级目录n
cd ..
return 0
}
PROJECT_DIR="/global/Binary/external/internal"
#递归删除所有二进制自研文件,里面不能存放任何非自研文件,因为所有文件都会删除。?
SCANFILE ${PROJECT_DIR}
echo "clean success!"
作者: compare2000    时间: 2014-05-15 11:38
Git 常用命令总结
符号约定
[]:可选     <>:必选


git 配置
git config [--global] user.name <name>        设置用户名

git config [--global] user.email <email>         设置邮箱

git config [--global] core.editor <editor>        设置编辑器

git config [--global] github.user <user>         设置github帐号名

git config [--global] github.token <token>        设置github的token

--global是对当前系统用户的全局设置,在~/.gitconfig中。对系统所有用户进行配置,/etc/gitconfig。对当前项目,.git/config


git创建库
git clone <url>                   ssh/http(s)/git三种协议,ssh和https可推送

git init                        初始化Git仓库


git日常操作
git add <file>                 将文件加入index file

git rm [--cached]                删除,加--cached表示仅从index file中删除文件,即放弃跟踪

git mv <src> <dest>             移动/更名

git diff --cached/--staged          当前索引与上次提交(有哪些需要commit)

git diff                      当前索引与工作目录(有哪些需要add)

git diff HEAD[^]               工作目录与上次提交(当前目录与上次提交有何改变)

git commit [-a] -m <msg>          提交

git commit --amend [-m <msg>]       修复上次提交

git reset HEAD <file>             同--mixed,default option

git reset --mixed HEAD            撤销 commit 和index file,只保留 working tree 的信息

git reset --hard HEAD[^]           将 working tree 和 index file 都撤销到以前状态

git reset --soft HEAD[^]            只撤销 commit,而保留 working tree 和 index file 的信息

                     回复到某个状态。以git reset --soft HEAD为例,commit回退到

                     HEAD(相当于无变化),若是HEAD^,则commit回退到HEAD^

git gc                     用垃圾回收机制清除由于 reset 而造成的垃圾代码

git status                  显示当前工作目录状态

git log [-p]                   显示提交历史(many useful options to be learned)

git branch [branch]               显示/新建分支

git branch -d/-D               删除分支(d表示“在分支合并后删除分支”,D表示无论如何都删除分支)

git show-branch

git checkout <branch>            切换分支(分支未commit无法切换)

git merge <branch>              合并分支

git merge == git pull .

git show <branch | commit | tag | etc>        显示对应对象的信息

git grep <rep> [object]             (在指定对象(历史记录)中)搜索        

git cat-file                    查看数据

git cat-file <-t | -s | -e | -p | (type)> <object>        type can be one of: blob, tree, commit, tag

git ls-files [--stage]              show information about files in the index and the working tree(实际是查看索引文件)

git watchchanged <since>..<until>       显示两个commit(当然也可以是branch)的区别

git remote [-v]                    显示远程仓库,加-v选项可显示仓库地址

git remote add <repo_name> <url>         添加远程仓库,repo_name为shortname,指代仓库地址

git remote rename <old_name> <new_name>    更名

git remote rm <repo_name>            删除远程仓库

git remote show <repo_name>          查看远程仓库信息

git remote fetch <repo_name>           从远程仓库抓取数据(并不合并)

git pull <repo_name> <branch_name>      拉去数据并合并到当前分支

git push <repo_name> <branch_name>      推送指定分支到指定仓库  例如: git push origin HEAD:refs/for/master

git fetch <repo_name> <branch_name>[:<local_branch_name>]    拉去数据,未合并


git相关环境变量
GIT_DIR: 如果指定了那么git init将会在GIT_DIR指定的目录下创建版本库

GIT_OBJECT_DIRECTORY: 用来指示对象存储目录的路径。即原来$GIT_DIR/objects下的文件会置于该变量指定的路径下


git常见变量
HEAD: 表示最近一次的 commit。

MERGE_HEAD: 如果是 merge 产生的 commit,那么它表示除 HEAD 之外的另一个父母分支。

FETCH_HEAD: 使用 git-fetch 获得的 object 和 ref 的信息都存储在这里,这些信息是为日后 git-merge 准备的。

HEAD^: 表示 HEAD 父母的信息

HEAD^^: 表示 HEAD 父母的父母的信息

HEAD~4: 表示 HEAD 上溯四代的信息

HEAD^1: 表示 HEAD 的第一个父母的信息

HEAD^2: 表示 HEAD 的第二个父母的信息

COMMIT_EDITMSG: 最后一次 commit 时的提交信息。



常用基本概念说明
1. Working Directory(工作目录)
Git的工作目录是保存当前正在工作的文件所在的目录,和working tree是相同的意思。在这个目录中的文件可能会在切换branch时被GIT删除或者替换。这个目录是个临时目录,临时存储你从GIT库中取出的文件,这些文件一直会被保存,直到下次提交。


2. GIT Directory(GIT库目录)
项目的所有历史提交都被保存在了GIT库目录中,只要你不作回滚操作,它应该不会丢失。


3. GIT Index(Git索引)
Git index 可以看作是工作目录和Git库目录之间的暂存区,和staging area是相同的意思。可以使用Git index构建一组你准备一起提交的改变。Git Index和Git Staging area是同一个意思,都是指已经被add的但尚未commit的那些内容所在的区域。最简单的查看目前什么内容在index中的方法是使用git status命令。

•命令中”Changes to be committed“中所列的内容是在Index中的内容,commit之后进入Git Directory。
•命令中“Changed but not updated”中所列的内容是在Working Directory中的内容,add之后将进入Index。
•命令中“Untracked files”中所列的内容是尚未被Git跟踪的内容,add之后进入Index。
哪些操作能够改变git index中的内容?
A). git add <path>...会将working directory中的内容添加进入git index。
B). git reset HEAD <path>...会将git index中path内容删除,重新放回working directory中






git reset用法小结


•reset命令的三种方式:
git reset -mixed:为默认方式,不带任何参数的git reset,即时这种方式,它退回到某一个版本,只保留源码,回退commit和index的消息,如果需要提交
                          就需要先git add 再git commit 或者直接git commit -a


git reset -soft :回退到某一个版本,只回退了commit信息,不会恢复到index file一级, 如果需要提供则直接git commit 即可


git rest -hard: 彻底回退到某个版本,本地的源码也会变为上一个版本的内容




•实例
       git reset HEAD^ 回退所有内容到上一版本


git reset HEAD^  test.cpp 回退文件text.cpp到上一个版本


git reset -soft HEAD~3 向前回退到第3个版本


git reset -hard origin/master 将本地状态回退到和远程的一样


git reset 055d 回退到某一个版本(055d)


git revert HEAD 回退到上一次提交的状态,按照某一次的commit完全反向进行的一次commit


git reset -hard base 111 本地仓库回退到某一个版本
作者: compare2000    时间: 2014-05-15 11:38
Repo和Git 版本管理常用命令总结  1. 服务器版本下载:

repo init -u git@192.168.1.11:i700t_60501010/platform/manifest.git -b froyo_almond -m M76XXTSNCJNLYA60501010.xml

repo sync

repo forall -c git checkout --track origin/i700t_60501010 -b [你的本地分支]

或者:

git clone git@192.168.1.11:6120_gingerbread/Arm9-6120.git

git checkout --track origin/arm9_6120 -b [你的本地分支名]



2. 服务器新加仓库同步:

请按如下步骤取得Lanucher_wpon的代码:

1:~> cd I700T/.repo/manifest

2: I700T/.repo/manifest> git pull --rebase

3: I700T/.repo/manifest> cd ../..

4: I700T> repo sync platform/packages/apps/Launcher_wpon

5:I700T> cd packages/apps/Launcher_wpon

6:I700T/packages/apps/Launcher_wpon>

7:git checkout --track origin/froyo_almond -b [你的本地分支]



3. 上传本地修改到服务器

repo forall -c git add .

repo forall -c git reset --hard

repo forall -c git pull --rebase        和服务器同步(要上传代码前,一般先进行此操作)

git add .    或git add 文件名            添加当前仓库修改的文件

git commit -m "..."                       在引号中添加你的修改记录

git push origin  本地分支名:froyo_almond       上传本地修改的代码



4. 设置一些默认的全局变量,对所有工程代码有效

git config --global user.name yourmail

git config --global user.email yourmail

git config --global push.default tracking     这样后续git push 后面不用带参数



5.查看修改记录

git log                       本地仓库修改记录

repo forall -c git log --since="2011-04-19" --until="2011-04-21"   按条件查看工程所有仓库修改记录

repo status                查看工程中所有仓库的修改状态(包括文件位置)

git status                   查看仓库修改状态



6. 分支相关

git branch                 查看本地branch

git branch -r              查看远程branch

git branch -a              查看所有branch

git branch -D  (-d)  (branch name)    删除branch

cat .git/config      可以查看本地branch一些信息



7. 修改恢复相关

git checkout filename1  filename2  ...           取消本地修改,和服务器同步

git stash  

git stash apply          先stash本地修改,然后执行git pull --rebase同步,最后再APPLY恢复自己的修改

git reset --soft head_commit  恢复到最后一次commit,保持代码修改

git reset --hard commit    恢复到指定一次commit,放弃之前所有修改

#回退a.py这个文件的版本到上一个版本  

git reset HEAD^ a.py

git reset commitNO filename



8. 本地某仓库出问题了,不好闹腾时,删除之,并重新同步跟踪

project_folder/vendor/qcom$ rm -rf proprietary/                         进到相应目录,删除之

project_folde$ repo sync platform/vendor/qcom/proprietary       重新repo sync,后面路径名称可查看:

                                                                                                 gedit .repo/manifest.xml

git branch -a    ----列举所有BRANCH

git branch -D 700_arm11_server

git branch -D 700_arm11_server_wifi  --删掉所有本地branch

git checkout --track origin/froyo_almond -b 700_arm11_server   然后track远程branch,重新创建本地分支



9.tag的使用

git tag [tag_name] [version],在对应版本上(一般用change的SHA1),创建tag

git tag -l 列出当前tag

git tag -d [tag_name] 删除tag

有了tag以后,可以使用git checkout [tag_name] -b [branch_name]来检出对应tag时刻的代码。也可以用tag name来实现diff等功能。



10. patch的使用

git diff filename1 filename2 ...                  修改位置对比,查看源码

git diff > xxx.patch                                  将修改的地方打成一个patch

git apply xxx.patch                                  将patch打上



11. 后续有用到的命令继续添加

git revert 是撤销某次提交。git reset –hard,才是退回到以前的版本

git reset --soft commitNum      保存代码修改的reset,但这个时候无法使用git diff 进行比较修改的文件,必须:

git reset filename filename     这样就可以git diff查看

git diff ffd98b291e0caa6c33575c1ef465eae661ce40c9 b8e7b00c02b95b320f14b625663fdecf2d63e74c 查看某两个版本之间的差异

git diff ffd98b291e0caa6c33575c1ef465eae661ce40c9:filename b8e7b00c02b95b320f14b625663fdecf2d63e74c:filename 查看某两个版本的某个文件之间的差异

  Git 命令别名

$ git config –global alias.co checkout // co将会成为checkout的别名

$ git config –global alias.br branch

$ git config –global alias.ci commit

$ git config –global alias.st status

$ git config –global user.name “username”

$ git config –global user.email username@mail.com




12. gitk使用

gitk & 可以让终端继续编写执行命令

gitk  文件名     可以查看某个文件的修改记录

gitk -50  ubuntu64位打开gitk会导致机子变卡,后面带数字可解决此问题




13 如何添加之前没有跟踪的文件

     比如kernel中要添加bin文件:打开这个库的.gitignore文件,然后删掉 bin 不跟踪,然后就可以git add bin

作者: compare2000    时间: 2014-05-15 11:40
Repo和Git 版本管理常用命令  一、服务器版本下载:

       repo init -u git@192.168.1.11:i700t_60501010/platform/manifest.git -b froyo_almond -m M76XXTSNCJNLYA60501010.xml

       repo sync

       repo forall -c git checkout --track origin/i700t_60501010 -b [你的本地分支]

       或者:

       git clone git@192.168.1.11:6120_gingerbread/Arm9-6120.git

       git checkout --track origin/arm9_6120 -b [你的本地分支名]

====================================================================================================================================


二、服务器新加仓库同步:

       请按如下步骤取得Lanucher_wpon的代码:

       1.~> cd I700T/.repo/manifest

       2. I700T/.repo/manifest> git pull --rebase

       3. I700T/.repo/manifest> cd ../..

       4. I700T> repo sync platform/packages/apps/Launcher_wpon

       5.I700T> cd packages/apps/Launcher_wpon

       6.I700T/packages/apps/Launcher_wpon>

       7.git checkout --track origin/froyo_almond -b [你的本地分支]

====================================================================================================================

三、上传本地修改到服务器

       repo forall -c git pull --rebase        和服务器同步(要上传代码前,一般先进行此操作)

       git add .    或git add 文件名            添加当前仓库修改的文件

       git commit -m "..."                       在引号中添加你的修改记录

       git push origin  本地分支名:froyo_almond       上传本地修改的代码

====================================================================================================================

四、设置一些默认的全局变量,对所有工程代码有效

       git config --global user.name yourmail

       git config --global user.email yourmail

       git config --global push.default tracking     这样后续git push 后面不用带参数

====================================================================================================================

五、查看修改记录

       git log                       本地仓库修改记录

       repo forall -c git log --since="2011-04-19" --until="2011-04-21"   按条件查看工程所有仓库修改记录

       repo status                查看工程中所有仓库的修改状态(包括文件位置)

       git status                   查看仓库修改状态

====================================================================================================================

六、分支相关

       git branch                 查看本地branch

       git branch -r              查看远程branch

       git branch -a              查看所有branch

       git branch -D  (-d)  (branch name)    删除branch

       cat .git/config      可以查看本地branch一些信息

====================================================================================================================

七、修改恢复相关

       git checkout filename1  filename2  ...           取消本地修改,和服务器同步

       git stash  

       git stash apply          先stash本地修改,然后执行git pull --rebase同步,最后再APPLY恢复自己的修改

       git reset --soft head_commit  恢复到最后一次commit,保持代码修改

       git reset --hard commit    恢复到指定一次commit,放弃之前所有修改

       #回退a.py这个文件的版本到上一个版本  

       git reset HEAD^ a.py

       git reset commitNO filename

====================================================================================================================

八、本地某仓库出问题了,不好闹腾时,删除之,并重新同步跟踪

       project_folder/vendor/qcom$ rm -rf proprietary/                         进到相应目录,删除之

       project_folde$ repo sync platform/vendor/qcom/proprietary       重新repo sync,后面路径名称可查看:gedit .repo/manifest.xml

       git branch -a    ----列举所有BRANCH

       git branch -D 700_arm11_server

       git branch -D 700_arm11_server_wifi  --删掉所有本地branch

       git checkout --track origin/froyo_almond -b 700_arm11_server   然后track远程branch,重新创建本地分支

====================================================================================================================

九、tag的使用

       git tag [tag_name] [version],在对应版本上(一般用change的SHA1),创建tag

       git tag -l 列出当前tag

       git tag -d [tag_name] 删除tag

       有了tag以后,可以使用git checkout [tag_name] -b [branch_name]来检出对应tag时刻的代码。也可以用tag name来实现diff等功能
====================================================================================================================

十、patch的使用

       git diff filename1 filename2 ...                修改位置对比,查看源码

       git diff > xxx.patch                                  将修改的地方打成一个patch

       git apply xxx.patch                                 将patch打上

====================================================================================================================

十一、后续有用到的命令继续添加

       git revert 是撤销某次提交。git reset –hard,才是退回到以前的版本

       git reset --soft commitNum      保存代码修改的reset,但这个时候无法使用git diff 进行比较修改的文件,必须:

       git reset filename filename     这样就可以git diff查看

       git diff ffd98b291e0caa6c33575c1ef465eae661ce40c9 b8e7b00c02b95b320f14b625663fdecf2d63e74c 查看某两个版本之间的差异

       git diff ffd98b291e0caa6c33575c1ef465eae661ce40c9:filename b8e7b00c02b95b320f14b625663fdecf2d63e74c:filename 查看某两个版本的          某个文件之间的差异

       Git 命令别名

       $ git config –global alias.co checkout // co将会成为checkout的别名

       $ git config –global alias.br branch

       $ git config –global alias.ci commit

       $ git config –global alias.st status

       $ git config –global user.name “username”

       $ git config –global user.email username@mail.com

作者: compare2000    时间: 2014-05-15 15:02
git repo使用心得
git是相当与svn,cvs等的版本控制工具,repo封装了对git的操作,使git命令更简单化,
git学习网站这是之前自己看的,但实际工作操作中还是遇到了很多困难,总是遇到冲突,无法提交等问题,现在对Git的使用还是不很熟,但现在对使用也基本了解了,这样简单介绍一下。
在使用git时,用了repo,Windows下开发,使用的是Cygwin工具,这个比git的bash更好使用。使用git一定要明白,add,commit操作都不会影像远程代码仓库,只有push了才会将代码推送到远程仓库,比svn多了一步;下面以本人的操作为例:
1. mkdir gitlearn
2.cd gitlearn
3.repo init -u ssh://xxx -m xxx.xml 拉取远程代码
这个其实就相当于git的这两部操作:
git init
git clone git@github.com:wufengxyz/hellogit.git <mailto:git@github.com:wufengxyz/hellogit.git> 这个是我在github上的一个仓库,这个网站也提供学习git的指南
4.repo start xxx <project..> 创建分支,在project工程上,可以是多个工程,这些工程是自己将要修改的,在Git使用中一定要开分支,不然冲突了就无法解决,
这个相当与git branch XXX(分支名 ) git checkout XXX,创建分支,切换到这个分支(repo可以操作多个工程、模块,而git是单工程的)
5.进入一个开了分支的工程,git branch -a,查看是否在分支上
6.git status,一定要多用次命令,查看修改等状态
7.开始在分支上修改,添加操作等等操作
8.修改过了代码,接下来使用
git add .
git commit -m "bug修复"
9.接下来可以同步远程代码了
repo sync xxx(工程名)
10.现在远程代码已经被down下来了,相信大家都只要会有冲突出现可能
有冲突修改,修改后使用git rebase --continue这样就相当于把这些修改合并到了上次冲突的提交中了
11.repo upload    这步操作是将代码推送到远程代码审核仓库,我们的leader会review我们的代码,通过了这次开发任务完成,不通过继续修改
12.review不通过要执行以下操作,这里要注意,我们的代码已经提交到远程仓库了,这是跟git rebase操作的一个区别
修改不通过的代码......修改完成后,git add .,git commit -m "xxx"完成一次commit了,接下来使用
git rebase -i HEAD~2
弹出一个界面
pick 0b92779 Test commit two
pick 9d80074 rebase test
合并两次pick,将第二个pick改为s,如下
pick 0b92779 Test commit two
s 9d80074 rebase test
esc :wq保存变更
弹出一个界面,删除后面一个Change-Id(dd操作,删除整行)
esc:wq保存变更
然后repo upload上传修改后的代码就完成了所有提交过程了,完全通过后就进行一次repo sync同步一下远程仓库代码,记住要经常用git status,或者repo status命令,每次做修改前一定要有第4步操作。
接下来介绍如果git完全不能提交使用了情况下的解决办法,经常因为没开分支造成;
把自己修改后的代码先备份出来,接下来repo sync把远程代码库中最新的代码同步下来,然后用备份的代码覆盖这些最新代码,如此操作后使用git status就会发现自己修改了的代码,然后在IDE工程中看与远程最新版本代码有什么不同,把远程代码中有,而自己当前代码中没有的加上,然后进行正常的提交过程就OK了,为什么这样大家想想就明白了相当于在最新版本代码上做了修改。
这些只是Git中最基本的操作流程,希望对刚刚使用git的同学有所帮助
作者: compare2000    时间: 2014-05-22 16:41
Linux 查cpu核数  
在Linux系统中,如何详细了解CPU的信息呢? 当然是通过cat /proc/cpuinfo来检查了,但是比如几个物理CPU/几核/几线程,这些问题怎么确定呢?
经过查看,我的开发机器是1个物理CPU,4核8线程,Intel(R) Core(TM) i7 CPU 860  @ 2.80GHz
记录一下,判断的过程和知识。

判断依据:
1.具有相同core id的cpu是同一个core的超线程。
2.具有相同physical id的cpu是同一颗cpu封装的线程或者cores。
英文版:
1.Physical id and core id are not necessarily consecutive but they are unique. Any cpu with the same core id are hyperthreads in the same core.
2.Any cpu with the same physical id are threads or cores in the same physical socket.

echo "logical CPU number:"
#逻辑CPU个数
cat /proc/cpuinfo | grep "processor" | wc -l

echo "physical CPU number:"
#物理CPU个数:
cat /proc/cpuinfo | grep "physical id" | sort -u | wc -l

echo "core number in a physical CPU:"
#每个物理CPU中Core的个数:
cat /proc/cpuinfo | grep "cpu cores" | uniq | awk -F: '{print $2}'
#查看core id的数量,即为所有物理CPU上的core的个数
cat /proc/cpuinfo | grep "core id" | uniq |  wc -l

#是否为超线程?
#如果有两个逻辑CPU具有相同的”core id”,那么超线程是打开的。或者siblings数目比cpu cores数目大。
#每个物理CPU中逻辑CPU(可能是core, threads或both)的个数:
cat /proc/cpuinfo | grep "siblings"


/proc/cpuinfo 文件包含系统上每个处理器的数据段落。/proc/cpuinfo 描述中有 6 个条目适用于多内核和超线程(HT)技术检查:processor, vendor id, physical id, siblings, core id 和 cpu cores。
processor 条目包括这一逻辑处理器的唯一标识符。
physical id 条目包括每个物理封装的唯一标识符。
core id 条目保存每个内核的唯一标识符。
siblings 条目列出了位于相同物理封装中的逻辑处理器的数量。
cpu cores 条目包含位于相同物理封装中的内核数量。
如果处理器为英特尔处理器,则 vendor id 条目中的字符串是 GenuineIntel。

1.拥有相同 physical id 的所有逻辑处理器共享同一个物理插座。每个 physical id 代表一个唯一的物理封装。
2.Siblings 表示位于这一物理封装上的逻辑处理器的数量。它们可能支持也可能不支持超线程(HT)技术。
3.每个 core id 均代表一个唯一的处理器内核。所有带有相同 core id 的逻辑处理器均位于同一个处理器内核上。
4.如果有一个以上逻辑处理器拥有相同的 core id 和 physical id,则说明系统支持超线程(HT)技术。
5.如果有两个或两个以上的逻辑处理器拥有相同的 physical id,但是 core id 不同,则说明这是一个多内核处理器。cpu cores 条目也可以表示是否支持多内核。

判断CPU是否64位,检查cpuinfo中的flags区段,看是否有lm标识。
Are the processors 64-bit?   
A 64-bit processor will have lm ("long mode" in the flags section of cpuinfo. A 32-bit processor will not.

Gitolite 构建 Git 服务器的权限管理  
参考资料:

    http://www.ossxp.com/doc/git/gitolite.html

架设环境debian squeeze,所有安装软件均为debian源上自带。

主要参考文献:

1、蒋鑫作品http://www.ossxp.com/doc/git/gitolite.html (堪称中文入门手册)

2、Gitolite自带文档https://github.com/sitaramc/gitolite/tree/master/doc (仔细阅读自带文档,会帮助解决细节问题)

3、Gitolite WIKI http://sitaramc.github.com/gitolite/   




Gitolite安装:

1、安装软件

sudo apt-get install gitolite git-core




2、产生G管理员的ssh key

确定某个账号,可以是server上的账号,也可以是某个客户端的账号,但是只能是唯一的。

在该账号下,用ssh-keygen产生ssh key。

在终端下运行ssh-keygen,默认在~/.ssh下产生文件 id_rsa.pub。

将这个文件放到服务器/tmp/下,命名为admin.pub。




2、建立git专用账号

sudo adduser --system --shell /bin/bash --group git

sudo adduser git ssh

sudo passwd git

以后上传下载工作都通过该账号进行,但是不直接使用该账号登陆,而是通过ssh key的认证。原理见文献1。




3、建立Gitolite仓库

su git进入git账号

gl-setup /tmp/admin.pub以admin.pub的账号为管理员,建立Gitolite仓库。

这时会在git账号下产生repositories目录,下面有两个子目录gitolite-admin.git和testing.git




4、初涉Gitolite管理

在G管理员账号下,运行 git clone git@server_ip:gitolite_admin.git

这样在G管理员帐号下,就有一个gitolite_admin的文件夹,里面是管理gitolite仓库的配置文件和Key文件。


增加用户:

将某人的ssh key文件abc.pub,复制到gitolite_admin/Keydir下;

然后 git add Keydir/abc.pub

git commit -m 'add abc'

git push origin master

这样就添加好了abc用户。

Gitolite是管理git用户的,但Gitolite本身又是通过git来操作的。很有意思吧!

相信删除的方法不用我讲了吧!




配置用户权限:

Gitolite是用来管理用户行为的,这个有conf/gitolite.conf完成。

下面所有例子来自参考文献1。由于个别地方已不适用,所以稍作修改。

下面我们看一个简单的授权文件:


1   @admin = jiangxin wangsheng 23   repo gitolite-admin4       RW+                 = jiangxin56   repo ossxp/..*7       C                   = @admin8       RW                  = @all910  repo testing11      RW+                         =   @admin12      RW      master              =   junio13      RW+     pu                  =   junio14      RW      cogito$             =   pasky15      RW      bw/                 =   linus16      -                           =   somebody17      RW      tmp/                =   @all18      RW      refs/tags/v[0-9]    =   junio在上面的示例中,我们演示了很多授权指令。

•第1行,定义了用户组 @admin,包含两个用户 jiangxin 和 wangsheng。

•第3-4行,定义了版本库 gitolite-admin。并指定只有用户 jiangxin 才能够访问,并拥有读(R)写(W)和强制更新(+)的权限。

•第6行,通过正则表达式定义了一组版本库,即在 ossxp/ 目录下的所有版本库。

•第7行,用户组 @admin 中的用户,可以在 ossxp/ 目录下创建版本库。

创建版本库的用户,具有对版本库操作的所有权限。

•第8行,所有用户都可以读写 ossxp 目录下的版本库,但不能强制更新。

(注意:RW和C分开两行是指软件仓库的权限,RWC在一行是分支的权限。见gitolite文档wildcard-repositories.mkd)

•第9行开始,定义的 testing 版本库授权使用了引用授权语法。

•第11行,用户组 @admin 对所有的分支和里程碑拥有读写、重置、添加和删除的授权。

•第12行,用户 junio 可以读写 master 分支。(还包括名字以 master 开头的其他分支,如果有的话)。

•第13行,用户 junio 可以读写、强制更新、创建以及删除 pu 开头的分支。

•第14行,用户 pasky 可以读写 cogito 分支。 (仅此分支,精确匹配)。




5、添加版本库

a、建一个空版本库

只需要在conf/gitolite.conf中添加一个规则,比如

repo work1

RW+=@all

这样gitolite就会自动创建一个空的版本库。

b、上传已有版本库

如上面的例子第6~8行中设定的规则,可以由admin(或指定其他账号),在指定目录下上传版本库。

在本机的版本库下执行:

git remote add r-server git@server_ipssxp/repo.git

git push r-server master

即推送完成。




6、高级管理


在4里,提到了大部分权限管理的情况。如果还不够用,就要进入通配符授权:

a、用户私有版本库

授权文件如下:

1  @administrators = jiangxin admin23  repo users/CREATOR/.+$4      C = @all5      R = @administrators说明:用户可以在自己的名字空间( /users/<userid>/ )下,自己创建版本库。

CREATOR是所有用户的通配符。创建者对版本库有最高权限,其他人只有读权限(当然可以另外设置)。

b、用户私有分支


授权文件:

repo test/repo4    RW+   dev/USER/          = @all    RW+   master         = @dev说明:

•所有用户都可以在 refs/heads/dev/<userid>/ (自己的名字空间)下创建、删除分支。但是不能修改其他人的分支。
•用户组 @dev 中的用户,对 master 分支具有读写和强制更新的权限,但是不能删除。
更改好配置文件后,同样适用git的方法,push到server上去。



7、Gitweb

上述操作的结果都没有直观的显示,这对将来的用户会造成一定的了解障碍。

这个障碍可以通过Gitweb来克服。Gitweb是一个网页前端,它的工作就是将git仓库的内容用网页形式展现出来。(如git.kernel.org上密密麻麻的仓库与分支)

这样开发者会更清楚地知道,去找自己需要的东西。

1、安装软件

sudo apt-get install gitweb

2、修改/etc/gitweb.conf

$projectroot = "/data/git-repo" <-- 此处应该是gitolite仓库的位置。我将之前git账号下的repositories移出来了,然后做了个链接回去(ln -s /data/git-repo /home/git/repositories)。

3、配置web

修改/etc/apache2/sites-available/default

我将原来的/var/www的内容换成了:

DocumentRoot /var/www                                                         
    <Directory /var/www>                                                           
        Options ExecCGI Indexes +FollowSymLinks MultiViews                        
        Order allow,deny                                                           
        Allow from all                                                            
        AllowOverride all                                                         
        AddHandler cgi-script cgi                                                  
        DirectoryIndex gitweb.cgi                                                  
        SetEnv  GITWEB_CONFIG  /etc/gitweb.conf                                    
    </Directory>     


然后,在/var/www下执行

sudo ln -s /usr/share/gitweb/* .

sudo ln -s gitweb.cgi index.html

这样进入http://server-ip就可以看到类似git.kernel.org的东东了。

对了,别忘了修改仓库的权限(chmod a+x /data/git-repo -R)

4、将www-data加入git组

作者: compare2000    时间: 2014-05-22 16:42
Linux操作系统下三种配置环境变量的方法  
在linux下做开发首先就是需要配置环境变量,下面以配置java环境变量为例介绍三种配置环境变量的方法。


1.修改/etc/profile文件

如果你的计算机仅仅作为开发使用时推荐使用这种方法,因为所有用户的shell都有权使用这些环境变量,可能会给系统带来安全性问题。



(1)用文本编辑器打开/etc/profile



(2)在profile文件末尾加入:

JAVA_HOME=/usr/share/jdk1.5.0_05

PATH=$JAVA_HOME/binPATH

CLASSPATH=.JAVA_HOME/lib/dt.jarJAVA_HOME/lib/tools.jar

export JAVA_HOME

export PATH

export CLASSPATH



(3)重新登录



注解:

a. 你要将 /usr/share/jdk1.5.0_05jdk 改为你的jdk安装目录



b. linux下用冒号“:”来分隔路径



c. $PATH / $CLASSPATH / $JAVA_HOME 是用来引用原来的环境变量的值,在设置环境变量时特别要注意不能把原来的值给覆盖掉了,这是一种常见的错误。



d. CLASSPATH中当前目录“.”不能丢,把当前目录丢掉也是常见的错误。



e. export是把这三个变量导出为全局变量。



f. 大小写必须严格区分。



2. 修改.bashrc文件  

这种方法更为安全,它可以把使用这些环境变量的权限控制到用户级别,如果你需要给某个用户权限使用这些环境变量,你只需要修改其个人用户主目录下的.bashrc文件就可以了。



(1)用文本编辑器打开用户目录下的.bashrc文件



(2)在.bashrc文件末尾加入:  

set JAVA_HOME=/usr/share/jdk1.5.0_05

export JAVA_HOME

set PATH=$JAVA_HOME/binPATH

export PATH

set CLASSPATH=.JAVA_HOME/lib/dt.jarJAVA_HOME/lib/tools.jar

export CLASSPATH



(3)重新登录



3. 直接在shell下设置变量

不赞成使用这种方法,因为换个shell,你的设置就无效了,因此这种方法仅仅是临时使用,以后要使用的时候又要重新设置,比较麻烦。



只需在shell终端执行下列命令:

export JAVA_HOME=/usr/share/jdk1.5.0_05

export PATH=$JAVA_HOME/binPATH

export CLASSPATH=.JAVA_HOME/lib/dt.jarJAVA_HOME/lib/tools.jar
git 通过ssh 进行认证连接  
首先安装git

emerge -av git  一条命令搞定,如果你使用ubuntu redhat 有相应的yum  apt-get 工具,



git麻烦在用户管理及管理上,下面上三种解决办法:

# 如果需要团队里的每个人都对仓库有写权限,又不能给每个人在服务器上建立账户,那么提供 SSH 连接就是唯一的选择了。我们假设用来共享仓库的服务器已经安装了 SSH 服务,而且你通过它访问服务器。  
#   
# 有好几个办法可以让团队的每个人都有访问权。

第一个办法 是给每个人建立一个账户,直截了当但过于繁琐。反复的运行 adduser 并且给所有人设定临时密码可不是好玩的。  
#   
# 第二个办法 是在主机上建立一个 git 账户,让每个需要写权限的人发送一个 SSH 公钥,然后将其加入 git 账户的 ~/.ssh /authorized_keys 文件。这样一来,所有人都将通过 git 账户访问主机。这丝毫不会影响提交的数据——访问主机用的身份不会影响 commit的记录。  
#   
# 另一个办法 是让 SSH 服务器通过某个 LDAP 服务,或者其他已经设定好的集中授权机制,来进行授权。只要每个人都能获得主机的 shell 访问权,任何可用的 SSH 授权机制都能达到相同效  # 如果需要团队里的每个人都对仓库有写权限,又不能给每个人在服务器上建立账户,那么提供 SSH 连接就是唯一的选择了。我们假设用来共享仓库的服务器已经安装了 SSH 服务,而且你通过它访问服务器。  

#   

为了简便选用了第二种办法 ,这种办法采用SSH公钥认证。

这里为了演示建立user1 user2 两个用户分别模拟两个开发人员,
                 建立git 用户,源码是通过git 用户进行初始化,可以将 它当作项目经理
并各自修改密码


产生公钥

ssh-keygen -C "你的email地址"  -t rsa

后面直接回车直到结束,中间可以不需要任何设置,该命令将生成一对非对称的公/私密钥,默认它们被存储在:
XP/2003用户:c:/Documents and Settings/登陆名/.ssh
Vista用户: c:/Users/登陆名/.ssh

linux          :~/.ssh

下面分别为user1 user2 产生公钥,私钥






这样有/home/user1/.ssh 下会产生两个文件,id_rsa 私钥,和id_rsa.pub公钥文件

公钥样子大概如此





该.ssh文件夹下面,私钥放在id_rsa文件里面,不用理会它;

对于user2 ,git 用户作相同处理,然后各自的目录会生成相应的文件





然后需要将user1 user2 各自的公钥文件提供给git 用户,





2、在linux服务器上将公钥加到git用户的authorized_keys文件中。
  可以参考:http://github.com/git-on-windows/rookies
  git用户的建立及设置参考:http://progit.org/book/zh/ch4-4.html

只要把它们加入 authorized_keys 文件(译注:本例加入到了文件尾部):



这样认证就建好了

然后用git 用户在/home/git目录下建一个库 project_repos.git/



然后启运服务  /etc/init.d/git-daemon restart

当然也要启动sshd  

                 /etc/init.d/sshd start

user1 连接 (这里测试时将porject_repos.git 改为repos.git 了, 此系小节,可忽略)

.git包含的目录含义  
UPDATE: I’ve recieved some very helpful comments regarding corrections to the various files and what they do. Thanks for letting me know and keeping me on my toes.

One of the things I like best about Git is that it keeps all of its information in one place: your .git directory in your project’s root. If you haven’t been digging around it yet, don’t fret! There’s plenty of goodies to be had within it. Let’s take a look into the important files and folders and try to get a better sense of what’s going on under the hood.

The basic structure looks like this:

.|-- COMMIT_EDITMSG|-- FETCH_HEAD|-- HEAD|-- ORIG_HEAD|-- branches|-- config|-- description|-- hooks|    |-- applypatch-msg|    |-- commit-msg|    |-- post-commit|    |-- post-receive|    |-- post-update|    |-- pre-applypatch|    |-- pre-commit|    |-- pre-rebase|    |-- prepare-commit-msg|    `-- update|-- index|-- info|   `-- exclude|-- logs|   |-- HEAD|   `-- refs|-- objects`-- refs    |-- heads    |-- remotes    |-- stash    `-- tagsLet’s go over some of the normal files that you may see living in the base directory:

•COMMIT_EDITMSG: This is the last commit’s message. It’s not actually used by Git at all, but it’s there mostly for your reference after you made a commit.
•config: Contains settings for this repository. Specific configuration variables can be dumped in here (and even aliases!) What this file is most used for is defining where remotes live and some core settings, such as if your repository is bare or not.
•description: If you’re using gitweb or firing up git instaweb, this will show up when you view your repository or the list of all versioned repositories.
•FETCH_HEAD: The SHAs of branch/remote heads that were updated during the last git fetch
•HEAD: The current ref that you’re looking at. In most cases it’s probably refs/heads/master
•index: The staging area with meta-data such as timestamps, file names and also SHAs of the files that are already wrapped up by Git.
•packed-refs: Packs away dormant refs, this is not the definitive list of refs in your repository (the refs folder has the real ones!) Take a look at gitster’s comment to see more information on this.
•ORIG_HEAD: When doing a merge, this is the SHA of the branch you’re merging into.
•MERGE_HEAD: When doing a merge, this is the SHA of the branch you’re merging from.
•MERGE_MODE: Used to communicate constraints that were originally given to git merge to git commit when a merge conflicts, and a separate git commit is needed to conclude it. Currently --no-ff is the only constraints passed this way.
•MERGE_MSG: Enumerates conflicts that happen during your current merge.
•RENAMED-REF: Still trying to track this one down. From a basic grep through the source, it seems like this file is related to errors when saving refs.
There’s plenty of directories as well:

•hooks: A directory that will fast become your best friend: this contains scripts that are executed at certain times when working with Git, such as after a commit or before a rebase. An entire series of articles will be coming about hooks.
•info: Relatively uninteresting except for the exclude file that lives inside of it. We’ve seen this before in the ignoring files article, but as a reminder, you can use this file to ignore files for this project, but beware! It’s not versioned like a .gitignore file would be.
•logs: Contains history for different branches. Seems to be used mostly with the reflog command.
•objects: Git’s internal warehouse of blobs, all indexed by SHAs.
•rebase-apply: The workbench for rebasing and for git am. You can dig into its patch file when it does not apply cleanly if you’re brave.
•refs: The master copy of all refs that live in your repository, be they for stashes, tags, remote tracking branches, or local branches.
Just one word of wisdom when messing around with Git internals: make sure you know what you’re doing, and if not, have a backup! Messing around with the config files or hooks is pretty simple but I wouldn’t go spelunking in the datastore if I didn’t have to. If for some reason you are as part of your normal workflow, you might be doing it wrong.

There’s plenty more about the internals of Git that we haven’t covered yet. One of the best guides is the Git Community Book, and of course, you can just download the source for yourself and take a look. If you have more information about what’s going on in the .git folder, let us know in the comments!

作者: compare2000    时间: 2014-05-22 16:44
Git详解二: Git基础  
Git 基础
读完本章你就能上手使用 Git 了。本章将介绍几个最基本的,也是最常用的 Git 命令,以后绝大多数时间里用到的也就是这几个命令。读完本章,你就能初始化一个新的代码仓库,做一些适当配置;开始或停止跟踪某些文件;暂存或提交某些更 新。我们还会展示如何让 Git 忽略某些文件,或是名称符合特定模式的文件;如何既快且容易地撤消犯下的小错误;如何浏览项目的更新历史,查看某两次更新之间的差异;以及如何从远程仓库 拉数据下来或者推数据上去。





2.1  取得项目的 Git 仓库
有两种取得 Git 项目仓库的方法。第一种是在现存的目录下,通过导入所有文件来创建新的 Git 仓库。第二种是从已有的 Git 仓库克隆出一个新的镜像仓库来。

在工作目录中初始化新仓库
要对现有的某个项目开始用 Git 管理,只需到此项目所在的目录,执行:

$ git init初始化后,在当前目录下会出现一个名为 .git 的目录,所有 Git 需要的数据和资源都存放在这个目录中。不过目前,仅仅是按照既有的结构框架初始化好了里边所有的文件和目录,但我们还没有开始跟踪管理项目中的任何一个文件。(在第九章我们会详细说明刚才创建的.git 目录中究竟有哪些文件,以及都起些什么作用。)

如果当前目录下有几个文件想要纳入版本控制,需要先用 git add 命令告诉 Git 开始对这些文件进行跟踪,然后提交:

$ git add *.c$ git add README$ git commit -m 'initial project version'稍后我们再逐一解释每条命令的意思。不过现在,你已经得到了一个实际维护着若干文件的 Git 仓库。

从现有仓库克隆
如果想对某个开源项目出一份力,可以先把该项目的 Git 仓库复制一份出来,这就需要用到 git clone 命令。如果你熟悉其他的 VCS 比如 Subversion,你可能已经注意到这里使用的是 clone 而不是 checkout。这是个非常重要的差别,Git 收取的是项目历史的所有数据(每一个文件的每一个版本),服务器上有的数据克隆之后本地也都有了。实际上,即便服务器的磁盘发生故障,用任何一个克隆出来 的客户端都可以重建服务器上的仓库,回到当初克隆时的状态(虽然可能会丢失某些服务器端的挂钩设置,但所有版本的数据仍旧还在,有关细节请参考第四章)。

克隆仓库的命令格式为 git clone 。比如,要克隆 Ruby 语言的 Git 代码仓库 Grit,可以用下面的命令:

$ git clone git://github.com/schacon/grit.git这会在当前目录下创建一个名为“grit”的目录,其中包含一个 .git 的目录,用于保存下载下来的所有版本记录,然后从中取出最新版本的文件拷贝。如果进入这个新建的grit 目录,你会看到项目中的所有文件已经在里边了,准备好后续的开发和使用。如果希望在克隆的时候,自己定义要新建的项目目录名称,可以在上面的命令末尾指定新的名字:

$ git clone git://github.com/schacon/grit.git mygrit唯一的差别就是,现在新建的目录成了 mygrit,其他的都和上边的一样。

Git 支持许多数据传输协议。之前的例子使用的是 git:// 协议,不过你也可以用 http(s):// 或者user@server:/path.git 表示的 SSH 传输协议。我们会在第四章详细介绍所有这些协议在服务器端该如何配置使用,以及各种方式之间的利弊。





2.2  记录每次更新到仓库
现在我们手上已经有了一个真实项目的 Git 仓库,并从这个仓库中取出了所有文件的工作拷贝。接下来,对这些文件作些修改,在完成了一个阶段的目标之后,提交本次更新到仓库。

请记住,工作目录下面的所有文件都不外乎这两种状态:已跟踪或未跟踪。已跟踪的文件是指本来就被纳入版本控制管理的文件,在上次快照中有它们的记 录,工作一段时间后,它们的状态可能是未更新,已修改或者已放入暂存区。而所有其他文件都属于未跟踪文件。它们既没有上次更新时的快照,也不在当前的暂存 区域。初次克隆某个仓库时,工作目录中的所有文件都属于已跟踪文件,且状态为未修改。

在编辑过某些文件之后,Git 将这些文件标为已修改。我们逐步把这些修改过的文件放到暂存区域,直到最后一次性提交所有这些暂存起来的文件,如此重复。所以使用 Git 时的文件状态变化周期如图 2-1 所示。



图 2-1. 文件的状态变化周期
检查当前文件状态
要确定哪些文件当前处于什么状态,可以用 git status 命令。如果在克隆仓库之后立即执行此命令,会看到类似这样的输出:

$ git status# On branch masternothing to commit (working directory clean)这说明你现在的工作目录相当干净。换句话说,当前没有任何跟踪着的文件,也没有任何文件在上次提交后更改过。此外,上面的信息还表明,当前目录下没 有出现任何处于未跟踪的新文件,否则 Git 会在这里列出来。最后,该命令还显示了当前所在的分支是 master,这是默认的分支名称,实际是可以修改的,现在先不用考虑。下一章我们就会详细讨论分支和引用。

现在让我们用 vim 编辑一个新文件 README,保存退出后运行 git status 会看到该文件出现在未跟踪文件列表中:

$ vim README $ git status # On branch master # Untracked files: # (use "git add ..." to include in what will be committed) # # README nothing added to commit but untracked files present (use "git add" to track)就是在“Untracked files”这行下面。Git 不会自动将之纳入跟踪范围,除非你明明白白地告诉它“我需要跟踪该文件”,因而不用担心把临时文件什么的也归入版本管理。不过现在的例子中,我们确实想要跟踪管理 README 这个文件。

跟踪新文件
使用命令 git add 开始跟踪一个新文件。所以,要跟踪 README 文件,运行:

$ git add README此时再运行 git status 命令,会看到 README 文件已被跟踪,并处于暂存状态:

$ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # new file: README #只要在 “Changes to be committed” 这行下面的,就说明是已暂存状态。如果此时提交,那么该文件此时此刻的版本将被留存在历史记录中。你可能会想起之前我们使用git init 后就运行了 git add 命令,开始跟踪当前目录下的文件。在 git add 后面可以指明要跟踪的文件或目录路径。如果是目录的话,就说明要递归跟踪该目录下的所有文件。(译注:其实git add 的潜台词就是把目标文件快照放入暂存区域,也就是 add file into staged area,同时未曾跟踪过的文件标记为需要跟踪。这样就好理解后续 add 操作的实际意义了。)

暂存已修改文件
现在我们修改下之前已跟踪过的文件 benchmarks.rb,然后再次运行 status 命令,会看到这样的状态报告:

$ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # new file: README # # Changed but not updated: # (use "git add ..." to update what will be committed) # # modified: benchmarks.rb #  文件 benchmarks.rb 出现在 “Changed but not updated” 这行下面,说明已跟踪文件的内容发生了变化,但还没有放到暂存区。要暂存这次更新,需要运行git add 命令(这是个多功能命令,根据目标文件的状态不同,此命令的效果也不同:可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等)。现在让我们运行git add 将 benchmarks.rb 放到暂存区,然后再看看 git status 的输出:

$ git add benchmarks.rb $ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # new file: README # modified: benchmarks.rb #现在两个文件都已暂存,下次提交时就会一并记录到仓库。假设此时,你想要在 benchmarks.rb 里再加条注释,重新编辑存盘后,准备好提交。不过且慢,再运行git status 看看:

$ vim benchmarks.rb $ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # new file: README # modified: benchmarks.rb # # Changed but not updated: # (use "git add ..." to update what will be committed) # # modified: benchmarks.rb #  怎么回事?benchmarks.rb 文件出现了两次!一次算未暂存,一次算已暂存,这怎么可能呢?好吧,实际上 Git 只不过暂存了你运行 git add 命令时的版本,如果现在提交,那么提交的是添加注释前的版本,而非当前工作目录中的版本。所以,运行了git add 之后又作了修订的文件,需要重新运行 git add 把最新版本重新暂存起来:

$ git add benchmarks.rb $ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # new file: README # modified: benchmarks.rb #忽略某些文件
一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。我们可以创建一个名为 .gitignore 的文件,列出要忽略的文件模式。来看一个实际的例子:

$ cat .gitignore*.[oa]*~第一行告诉 Git 忽略所有以 .o 或 .a 结尾的文件。一般这类对象文件和存档文件都是编译过程中出现的,我们用不着跟踪它们的版本。第二行告诉 Git 忽略所有以波浪符(~)结尾的文件,许多文本编辑软件(比如 Emacs)都用这样的文件名保存副本。此外,你可能还需要忽略 log,tmp 或者 pid 目录,以及自动生成的文档等等。要养成一开始就设置好 .gitignore 文件的习惯,以免将来误提交这类无用的文件。

文件 .gitignore 的格式规范如下:

•所有空行或者以注释符号 # 开头的行都会被 Git 忽略。
•可以使用标准的 glob 模式匹配。 * 匹配模式最后跟反斜杠(/)说明要忽略的是目录。 * 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(!)取反。
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。星号(*)匹配零个或多个任意字符;[abc] 匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(?)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如[0-9] 表示匹配所有 0 到 9 的数字)。

我们再看一个 .gitignore 文件的例子:

# 此为注释 – 将被 Git 忽略*.a       # 忽略所有 .a 结尾的文件!lib.a    # 但 lib.a 除外/TODO     # 仅仅忽略项目根目录下的 TODO 文件,不包括 subdir/TODObuild/    # 忽略 build/ 目录下的所有文件doc/*.txt # 会忽略 doc/notes.txt 但不包括 doc/server/arch.txt查看已暂存和未暂存的更新
实际上 git status 的显示比较简单,仅仅是列出了修改过的文件,如果要查看具体修改了什么地方,可以用 git diff 命令。稍后我们会详细介绍git diff,不过现在,它已经能回答我们的两个问题了:当前做的哪些更新还没有暂存?有哪些更新已经暂存起来准备好了下次提交? git diff 会使用文件补丁的格式显示具体添加和删除的行。

假如再次修改 README 文件后暂存,然后编辑 benchmarks.rb 文件后先别暂存,运行 status 命令,会看到:

$ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # new file: README # # Changed but not updated: # (use "git add ..." to update what will be committed) # # modified: benchmarks.rb #  要查看尚未暂存的文件更新了哪些部分,不加参数直接输入 git diff:

$ git diffdiff --git a/benchmarks.rb b/benchmarks.rbindex 3cb747f..da65585 100644--- a/benchmarks.rb+++ b/benchmarks.rb@@ -36,6 +36,10 @@ def main  @commit.parents[0].parents[0].parents[0]  end  +  run_code(x, 'commits 1') do      +  git.commits.size    +  end      +         run_code(x, 'commits 2')do           log = git.commits('master', 15)           log.size此命令比较的是工作目录中当前文件和暂存区域快照之间的差异,也就是修改之后还没有暂存起来的变化内容。

若要看已经暂存起来的文件和上次提交时的快照之间的差异,可以用 git diff --cached 命令。(Git 1.6.1 及更高版本还允许使用git diff --staged,效果是相同的,但更好记些。)来看看实际的效果:

$ git diff --cacheddiff --git a/README b/READMEnew file mode 100644index 0000000..03902a1--- /dev/null+++ b/README2@@ -0,0 +1,5 @@+grit+ by Tom Preston-Werner, Chris Wanstrath+ [url]http://github.com/mojombo/grit++Grit
is a Ruby library for extracting information from a Git repository请注意,单单 git diff 不过是显示还没有暂存起来的改动,而不是这次工作和上次提交之间的差异。所以有时候你一下子暂存了所有更新过的文件后,运行git diff 后却什么也没有,就是这个原因。

像之前说的,暂存 benchmarks.rb 后再编辑,运行 git status 会看到暂存前后的两个版本:

$ git add benchmarks.rb$ echo '# test line' >> benchmarks.rb$ git status# On branch master## Changes to be committed:##        modified:   benchmarks.rb## Changed but not updated:##        modified:   benchmarks.rb#现在运行 git diff 看暂存前后的变化:

$ git diffdiff --git a/benchmarks.rb b/benchmarks.rbindex e445e28..86b2f7c 100644--- a/benchmarks.rb+++ b/benchmarks.rb@@ -127,3 +127,4 @@ end main()  ##pp Grit::GitRuby.cache_client.stats+# test line然后用 git diff --cached 查看已经暂存起来的变化:

$ git diff --cacheddiff --git a/benchmarks.rb b/benchmarks.rbindex 3cb747f..e445e28 100644--- a/benchmarks.rb+++ b/benchmarks.rb@@ -36,6 +36,10 @@ def main          @commit.parents[0].parents[0].parents[0]        end+        run_code(x, 'commits 1') do+          git.commits.size+        end+        run_code(x, 'commits 2') do          log = git.commits('master', 15)          log.size提交更新
现在的暂存区域已经准备妥当可以提交了。在此之前,请一定要确认还有什么修改过的或新建的文件还没有 git add 过,否则提交的时候不会记录这些还没暂存起来的变化。所以,每次准备提交前,先用git status 看下,是不是都已暂存起来了,然后再运行提交命令 git commit:

$ git commit这种方式会启动文本编辑器以便输入本次提交的说明。(默认会启用 shell 的环境变量 $EDITOR 所指定的软件,一般都是 vim 或 emacs。当然也可以按照第一章介绍的方式,使用git config --global core.editor 命令设定你喜欢的编辑软件。)

编辑器会显示类似下面的文本信息(本例选用 Vim 的屏显方式展示):

# Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # new file: README # modified: benchmarks.rb ~ ~ ~ ".git/COMMIT_EDITMSG" 10L, 283C可以看到,默认的提交消息包含最后一次运行 git status 的输出,放在注释行里,另外开头还有一空行,供你输入提交说明。你完全可以去掉这些注释行,不过留着也没关系,多少能帮你回想起这次更新的内容有哪些。(如果觉得这还不够,可以用-v 选项将修改差异的每一行都包含到注释中来。)退出编辑器时,Git 会丢掉注释行,将说明内容和本次更新提交到仓库。

另外也可以用 -m 参数后跟提交说明的方式,在一行命令中提交更新:

$ git commit -m "Story 182: Fix benchmarks for speed"[master]: created 463dc4f: "Fix benchmarks for speed" 2 files changed, 3 insertions(+), 0 deletions(-) create mode 100644 README好,现在你已经创建了第一个提交!可以看到,提交后它会告诉你,当前是在哪个分支(master)提交的,本次提交的完整 SHA-1 校验和是什么(463dc4f),以及在本次提交中,有多少文件修订过,多少行添改和删改过。

记住,提交时记录的是放在暂存区域的快照,任何还未暂存的仍然保持已修改状态,可以在下次提交时纳入版本管理。每一次运行提交操作,都是对你项目作一次快照,以后可以回到这个状态,或者进行比较。

跳过使用暂存区域
尽管使用暂存区域的方式可以精心准备要提交的细节,但有时候这么做略显繁琐。Git 提供了一个跳过使用暂存区域的方式,只要在提交的时候,给 git commit 加上-a 选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add 步骤:

$ git status# On branch master## Changed but not updated:##        modified:   benchmarks.rb#$ git commit -a -m 'added new benchmarks'[master 83e38c7] added new benchmarks 1 files changed, 5 insertions(+), 0 deletions(-)看到了吗?提交之前不再需要 git add 文件 benchmarks.rb 了。

移除文件
要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。可以用 git rm 命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。

如果只是简单地从工作目录中手工删除文件,运行 git status 时就会在 “Changed but not updated” 部分(也就是_未暂存_清单)看到:

$ rm grit.gemspec $ git status # On branch master # # Changed but not updated: # (use "git add/rm ..." to update what will be committed) # # deleted: grit.gemspec #然后再运行 git rm 记录此次移除文件的操作:

$ git rm grit.gemspec rm 'grit.gemspec' $ git status # On branch master # # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # deleted: grit.gemspec #最后提交的时候,该文件就不再纳入版本管理了。如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项 -f(译注:即 force 的首字母),以防误删除文件后丢失修改的内容。

另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。换句话说,仅是从跟踪清单中删除。比如一些大型日志文件或者一堆.a 编译文件,不小心纳入仓库后,要移除跟踪但不删除文件,以便稍后在 .gitignore 文件中补上,用 --cached 选项即可:

$ git rm --cached readme.txt后面可以列出文件或者目录的名字,也可以使用 glob 模式。比方说:

$ git rm log/\*.log注意到星号 * 之前的反斜杠 \,因为 Git 有它自己的文件模式扩展匹配方式,所以我们不用 shell 来帮忙展开(译注:实际上不加反斜杠也可以运行,只不过按照 shell 扩展的话,仅仅删除指定目录下的文件而不会递归匹配。上面的例子本来就指定了目录,所以效果等同,但下面的例子就会用递归方式匹配,所以必须加反斜 杠。)。此命令删除所有log/ 目录下扩展名为 .log 的文件。类似的比如:

$ git rm \*~会递归删除当前目录及其子目录中所有 ~ 结尾的文件。

移动文件
不像其他的 VCS 系统,Git 并不跟踪文件移动操作。如果在 Git 中重命名了某个文件,仓库中存储的元数据并不会体现出这是一次改名操作。不过 Git 非常聪明,它会推断出究竟发生了什么,至于具体是如何做到的,我们稍后再谈。

既然如此,当你看到 Git 的 mv 命令时一定会困惑不已。要在 Git 中对文件改名,可以这么做:

$ git mv file_from file_to它会恰如预期般正常工作。实际上,即便此时查看状态信息,也会明白无误地看到关于重命名操作的说明:

$ git mv README.txt README $ git status # On branch master # Your branch is ahead of 'origin/master' by 1 commit. # # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # renamed: README.txt -> README #其实,运行 git mv 就相当于运行了下面三条命令:

$ mv README.txt README$ git rm README.txt$ git add README如此分开操作,Git 也会意识到这是一次改名,所以不管何种方式都一样。当然,直接用 git mv 轻便得多,不过有时候用其他工具批处理改名的话,要记得在提交前删除老的文件名,再添加新的文件名。



2.3  查看提交历史
在提交了若干更新之后,又或者克隆了某个项目,想回顾下提交历史,可以使用 git log 命令查看。

接下来的例子会用我专门用于演示的 simplegit 项目,运行下面的命令获取该项目源代码:

git clone git://github.com/schacon/simplegit-progit.git然后在此项目中运行 git log,应该会看到下面的输出:

$ git log commit ca82a6dff817ec66f44342007202690a93763949 Author: Scott Chacon Date: Mon Mar 17 21:52:11 2008 -0700 changed the version number commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Author: Scott Chacon Date: Sat Mar 15 16:40:33 2008 -0700 removed unnecessary test code commit a11bef06a3f659402fe7563abf99ad00de2209e6 Author: Scott Chacon Date: Sat Mar 15 10:31:28 2008 -0700 first commit   默认不用任何参数的话,git log 会按提交时间列出所有的更新,最近的更新排在最上面。看到了吗,每次更新都有一个 SHA-1 校验和、作者的名字和电子邮件地址、提交时间,最后缩进一个段落显示提交说明。

git log 有许多选项可以帮助你搜寻感兴趣的提交,接下来我们介绍些最常用的。

我们常用 -p 选项展开显示每次提交的内容差异,用 -2 则仅显示最近的两次更新:

$ git log -p -2 commit ca82a6dff817ec66f44342007202690a93763949 Author: Scott Chacon Date: Mon Mar 17 21:52:11 2008 -0700 changed the version number diff --git a/Rakefile b/Rakefile index a874b73..8f94139 100644 --- a/Rakefile +++ b/Rakefile @@ -5,7 +5,7 @@ require 'rake/gempackagetask' spec = Gem::Specification.new do |s| - s.version = "0.1.0" + s.version = "0.1.1" s.author = "Scott Chacon" commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Author: Scott Chacon Date: Sat Mar 15 16:40:33 2008 -0700 removed unnecessary test code diff --git a/lib/simplegit.rb b/lib/simplegit.rb index a0a60ae..47c6340 100644 --- a/lib/simplegit.rb +++ b/lib/simplegit.rb @@ -18,8 +18,3 @@ class SimpleGit end end - -if $0 == __FILE__ - git = SimpleGit.new - puts git.show -end \ No newline at end of file  在做代码审查,或者要快速浏览其他协作者提交的更新都作了哪些改动时,就可以用这个选项。此外,还有许多摘要选项可以用,比如 --stat,仅显示简要的增改行数统计:

$ git log --stat commit ca82a6dff817ec66f44342007202690a93763949 Author: Scott Chacon Date: Mon Mar 17 21:52:11 2008 -0700 changed the version number Rakefile | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Author: Scott Chacon Date: Sat Mar 15 16:40:33 2008 -0700 removed unnecessary test code lib/simplegit.rb | 5 ----- 1 files changed, 0 insertions(+), 5 deletions(-) commit a11bef06a3f659402fe7563abf99ad00de2209e6 Author: Scott Chacon Date: Sat Mar 15 10:31:28 2008 -0700 first commit README | 6 ++++++ Rakefile | 23 +++++++++++++++++++++++ lib/simplegit.rb | 25 +++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 0 deletions(-)   每个提交都列出了修改过的文件,以及其中添加和移除的行数,并在最后列出所有增减行数小计。还有个常用的 --pretty 选项,可以指定使用完全不同于默认格式的方式展示提交历史。比如用oneline 将每个提交放在一行显示,这在提交数很大时非常有用。另外还有 short,full 和fuller 可以用,展示的信息或多或少有些不同,请自己动手实践一下看看效果如何。

$ git log --pretty=onelineca82a6dff817ec66f44342007202690a93763949 changed the version number085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 removed unnecessary test codea11bef06a3f659402fe7563abf99ad00de2209e6 first commit但最有意思的是 format,可以定制要显示的记录格式,这样的输出便于后期编程提取分析,像这样:

$ git log --pretty=format:"%h - %an, %ar : %s"ca82a6d - Scott Chacon, 11 months ago : changed the version number085bb3b - Scott Chacon, 11 months ago : removed unnecessary test codea11bef0 - Scott Chacon, 11 months ago : first commit表 2-1 列出了常用的格式占位符写法及其代表的意义。

选项         说明%H        提交对象(commit)的完整哈希字串%h        提交对象的简短哈希字串%T        树对象(tree)的完整哈希字串%t        树对象的简短哈希字串%P        父对象(parent)的完整哈希字串%p        父对象的简短哈希字串%an        作者(author)的名字%ae        作者的电子邮件地址%ad        作者修订日期(可以用 -date= 选项定制格式)%ar        作者修订日期,按多久以前的方式显示%cn        提交者(committer)的名字%ce        提交者的电子邮件地址%cd        提交日期%cr        提交日期,按多久以前的方式显示%s        提交说明你一定奇怪_作者(author)_和_提交者(committer)_之间究竟有何差别,其实作者指的是实际作出修改的人,提交者指的是最后将此 工作成果提交到仓库的人。所以,当你为某个项目发布补丁,然后某个核心成员将你的补丁并入项目时,你就是作者,而那个核心成员就是提交者。我们会在第五章 再详细介绍两者之间的细微差别。

用 oneline 或 format 时结合 --graph 选项,可以看到开头多出一些 ASCII 字符串表示的简单图形,形象地展示了每个提交所在的分支及其分化衍合情况。在我们之前提到的 Grit 项目仓库中可以看到:

$ git log --pretty=format:"%h %s" --graph* 2d3acf9 ignore errors from SIGCHLD on trap*  5e3ee11 Merge branch 'master' of git://github.com/dustin/grit|\| * 420eac9 Added a method for getting the current branch.* | 30e367c timeout code and tests* | 5a09431 add timeout protection to grit* | e1193f8 support for heads with slashes in them|/* d6016bc require time for xmlschema*  11d191e Merge branch 'defunkt' into local以上只是简单介绍了一些 git log 命令支持的选项。表 2-2 还列出了一些其他常用的选项及其释义。

选项 说明-p 按补丁格式显示每个更新之间的差异。--stat 显示每次更新的文件修改统计信息。--shortstat 只显示 --stat 中最后的行数修改添加移除统计。--name-only 仅在提交信息后显示已修改的文件清单。--name-status 显示新增、修改、删除的文件清单。--abbrev-commit 仅显示 SHA-1 的前几个字符,而非所有的 40 个字符。--relative-date 使用较短的相对时间显示(比如,“2 weeks ago”)。--graph 显示 ASCII 图形表示的分支合并历史。--pretty 使用其他格式显示历史提交信息。可用的选项包括 oneline,short,full,fuller 和 format(后跟指定格式)。限制输出长度
除了定制输出格式的选项之外,git log 还有许多非常实用的限制输出长度的选项,也就是只输出部分提交信息。之前我们已经看到过 -2 了,它只显示最近的两条提交,实际上,这是 - 选项的写法,其中的 n 可以是任何自然数,表示仅显示最近的若干条提交。不过实践中我们是不太用这个选项的,Git 在输出所有提交时会自动调用分页程序(less),要看更早的更新只需翻到下页即可。

另外还有按照时间作限制的选项,比如 --since 和 --until。下面的命令列出所有最近两周内的提交:

$ git log --since=2.weeks你可以给出各种时间格式,比如说具体的某一天(“2008-01-15”),或者是多久以前(“2 years 1 day 3 minutes ago”)。

还可以给出若干搜索条件,列出符合的提交。用 --author 选项显示指定作者的提交,用 --grep 选项搜索提交说明中的关键字。(请注意,如果要得到同时满足这两个选项搜索条件的提交,就必须用--all-match 选项。)

如果只关心某些文件或者目录的历史提交,可以在 git log 选项的最后指定它们的路径。因为是放在最后位置上的选项,所以用两个短划线(--)隔开之前的选项和后面限定的路径名。

表 2-3 还列出了其他常用的类似选项。

选项 说明-(n)        仅显示最近的 n 条提交--since, --after 仅显示指定时间之后的提交。--until, --before 仅显示指定时间之前的提交。--author 仅显示指定作者相关的提交。--committer 仅显示指定提交者相关的提交。来看一个实际的例子,如果要查看 Git 仓库中,2008 年 10 月期间,Junio Hamano 提交的但未合并的测试脚本(位于项目的 t/ 目录下的文件),可以用下面的查询命令:

$ git log --pretty="%h - %s" --author=gitster --since="2008-10-01" \   --before="2008-11-01" --no-merges -- t/5610e3b - Fix testcase failure when extended attributeacd3b9e - Enhance hold_lock_file_for_{update,append}()f563754 - demonstrate breakage of detached checkout wid1a43f2 - reset --hard/read-tree --reset -u: remove un51a94af - Fix "checkout --track -b newbranch" on detacb0ad11e - pull: allow "git pull origin $somethingcurGit 项目有 20,000 多条提交,但我们给出搜索选项后,仅列出了其中满足条件的 6 条。

使用图形化工具查阅提交历史
有时候图形化工具更容易展示历史提交的变化,随 Git 一同发布的 gitk 就是这样一种工具。它是用 Tcl/Tk 写成的,基本上相当于 git log 命令的可视化版本,凡是git log 可以用的选项也都能用在 gitk 上。在项目工作目录中输入 gitk 命令后,就会启动图 2-2 所示的界面。



图 2-2. gitk 的图形界面
上半个窗口显示的是历次提交的分支祖先图谱,下半个窗口显示当前点选的提交对应的具体差异。


作者: compare2000    时间: 2014-05-22 16:45
2.4  撤消操作
任何时候,你都有可能需要撤消刚才所做的某些操作。接下来,我们会介绍一些基本的撤消操作相关的命令。请注意,有些操作并不总是可以撤消的,所以请务必谨慎小心,一旦失误,就有可能丢失部分工作成果。

修改最后一次提交
有时候我们提交完了才发现漏掉了几个文件没有加,或者提交信息写错了。想要撤消刚才的提交操作,可以使用 --amend 选项重新提交:

$ git commit --amend此命令将使用当前的暂存区域快照提交。如果刚才提交完没有作任何改动,直接运行此命令的话,相当于有机会重新编辑提交说明,但将要提交的文件快照和之前的一样。

启动文本编辑器后,会看到上次提交时的说明,编辑它确认没问题后保存退出,就会使用新的提交说明覆盖刚才失误的提交。

如果刚才提交时忘了暂存某些修改,可以先补上暂存操作,然后再运行 --amend 提交:

$ git commit -m 'initial commit'$ git add forgotten_file$ git commit --amend上面的三条命令最终只是产生一个提交,第二个提交命令修正了第一个的提交内容。

取消已经暂存的文件
接下来的两个小节将演示如何取消暂存区域中的文件,以及如何取消工作目录中已修改的文件。不用担心,查看文件状态的时候就提示了该如何撤消,所以不需要死记硬背。来看下面的例子,有两个修改过的文件,我们想要分开提交,但不小心用git add . 全加到了暂存区域。该如何撤消暂存其中的一个文件呢?其实,git status 的命令输出已经告诉了我们该怎么做:

$ git add . $ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # modified: README.txt # modified: benchmarks.rb #就在 “Changes to be committed” 下面,括号中有提示,可以使用 git reset HEAD ... 的方式取消暂存。好吧,我们来试试取消暂存 benchmarks.rb 文件:

$ git reset HEAD benchmarks.rb benchmarks.rb: locally modified $ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # modified: README.txt # # Changed but not updated: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # modified: benchmarks.rb #   这条命令看起来有些古怪,先别管,能用就行。现在 benchmarks.rb 文件又回到了之前已修改未暂存的状态。

取消对文件的修改
如果觉得刚才对 benchmarks.rb 的修改完全没有必要,该如何取消修改,回到之前的状态(也就是修改之前的版本)呢?git status 同样提示了具体的撤消方法,接着上面的例子,现在未暂存区域看起来像这样:

# Changed but not updated: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # modified: benchmarks.rb #  在第二个括号中,我们看到了抛弃文件修改的命令(至少在 Git 1.6.1 以及更高版本中会这样提示,如果你还在用老版本,我们强烈建议你升级,以获取最佳的用户体验),让我们试试看:

$ git checkout -- benchmarks.rb $ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # modified: README.txt #可以看到,该文件已经恢复到修改前的版本。你可能已经意识到了,这条命令有些危险,所有对文件的修改都没有了,因为我们刚刚把之前版本的文件复制过 来重写了此文件。所以在用这条命令前,请务必确定真的不再需要保留刚才的修改。如果只是想回退版本,同时保留刚才的修改以便将来继续工作,可以用下章介绍 的 stashing 和分支来处理,应该会更好些。

记住,任何已经提交到 Git 的都可以被恢复。即便在已经删除的分支中的提交,或者用 --amend 重新改写的提交,都可以被恢复(关于数据恢复的内容见第九章)。所以,你可能失去的数据,仅限于没有提交过的,对 Git 来说它们就像从未存在过一样。



2.5  远程仓库的使用
要参与任何一个 Git 项目的协作,必须要了解该如何管理远程仓库。远程仓库是指托管在网络上的项目仓库,可能会有好多个,其中有些你只能读,另外有些可以写。同他人协作开发某 个项目时,需要管理这些远程仓库,以便推送或拉取数据,分享各自的工作进展。管理远程仓库的工作,包括添加远程库,移除废弃的远程库,管理各式远程库分 支,定义是否跟踪这些分支,等等。本节我们将详细讨论远程库的管理和使用。

查看当前的远程库
要查看当前配置有哪些远程仓库,可以用 git remote 命令,它会列出每个远程库的简短名字。在克隆完某个项目后,至少可以看到一个名为 origin 的远程库,Git 默认使用这个名字来标识你所克隆的原始仓库:

$ git clone git://github.com/schacon/ticgit.gitInitialized empty Git repository in /private/tmp/ticgit/.git/remote: Counting objects: 595, done.remote: Compressing objects: 100% (269/269), done.remote: Total 595 (delta 255), reused 589 (delta 253)Receiving objects: 100% (595/595), 73.31 KiB | 1 KiB/s, done.Resolving deltas: 100% (255/255), done.$ cd ticgit$ git remoteorigin也可以加上 -v 选项(译注:此为 --verbose 的简写,取首字母),显示对应的克隆地址:

$ git remote -vorigin        git://github.com/schacon/ticgit.git如果有多个远程仓库,此命令将全部列出。比如在我的 Grit 项目中,可以看到:

$ cd grit$ git remote -vbakkdoor  git://github.com/bakkdoor/grit.gitcho45     git://github.com/cho45/grit.gitdefunkt   git://github.com/defunkt/grit.gitkoke      git://github.com/koke/grit.gitorigin    git@github.com:mojombo/grit.git这样一来,我就可以非常轻松地从这些用户的仓库中,拉取他们的提交到本地。请注意,上面列出的地址只有 origin 用的是 SSH URL 链接,所以也只有这个仓库我能推送数据上去(我们会在第四章解释原因)。

添加远程仓库
要添加一个新的远程仓库,可以指定一个简单的名字,以便将来引用,运行 git remote add [shortname] [url]:

$ git remoteorigin$ git remote add pb git://github.com/paulboone/ticgit.git$ git remote -vorigin        git://github.com/schacon/ticgit.gitpb        git://github.com/paulboone/ticgit.git现在可以用字串 pb 指代对应的仓库地址了。比如说,要抓取所有 Paul 有的,但本地仓库没有的信息,可以运行 git fetch pb:

$ git fetch pbremote: Counting objects: 58, done.remote: Compressing objects: 100% (41/41), done.remote: Total 44 (delta 24), reused 1 (delta 0)Unpacking objects: 100% (44/44), done.From git://github.com/paulboone/ticgit * [new branch]      master     -> pb/master * [new branch]      ticgit     -> pb/ticgit现在,Paul 的主干分支(master)已经完全可以在本地访问了,对应的名字是 pb/master,你可以将它合并到自己的某个分支,或者切换到这个分支,看看有些什么有趣的更新。

从远程仓库抓取数据
正如之前所看到的,可以用下面的命令从远程仓库抓取数据到本地:

$ git fetch [remote-name]此命令会到远程仓库中拉取所有你本地仓库中还没有的数据。运行完成后,你就可以在本地访问该远程仓库中的所有分支,将其中某个分支合并到本地,或者只是取出某个分支,一探究竟。(我们会在第三章详细讨论关于分支的概念和操作。)

如果是克隆了一个仓库,此命令会自动将远程仓库归于 origin 名下。所以,git fetch origin 会抓取从你上次克隆以来别人上传到此远程仓库中的所有更新(或是上次 fetch 以来别人提交的更新)。有一点很重要,需要记住,fetch 命令只是将远端的数据拉到本地仓库,并不自动合并到当前工作分支,只有当你确实准备好了,才能手工合并。

如果设置了某个分支用于跟踪某个远端仓库的分支(参见下节及第三章的内容),可以使用 git pull 命令自动抓取数据下来,然后将远端分支自动合并到本地仓库中当前分支。在日常工作中我们经常这么用,既快且好。实际上,默认情况下git clone 命令本质上就是自动创建了本地的 master 分支用于跟踪远程仓库中的 master 分支(假设远程仓库确实有 master 分支)。所以一般我们运行git pull,目的都是要从原始克隆的远端仓库中抓取数据后,合并到工作目录中的当前分支。

推送数据到远程仓库
项目进行到一个阶段,要同别人分享目前的成果,可以将本地仓库中的数据推送到远程仓库。实现这个任务的命令很简单: git push [remote-name] [branch-name]。如果要把本地的 master 分支推送到origin 服务器上(再次说明下,克隆操作会自动使用默认的 master 和 origin 名字),可以运行下面的命令:

$ git push origin master只有在所克隆的服务器上有写权限,或者同一时刻没有其他人在推数据,这条命令才会如期完成任务。如果在你推数据前,已经有其他人推送了若干更新,那 你的推送操作就会被驳回。你必须先把他们的更新抓取到本地,合并到自己的项目中,然后才可以再次推送。有关推送数据到远程仓库的详细内容见第三章。

查看远程仓库信息
我们可以通过命令 git remote show [remote-name] 查看某个远程仓库的详细信息,比如要看所克隆的 origin 仓库,可以运行:

$ git remote show origin* remote origin  URL: git://github.com/schacon/ticgit.git  Remote branch merged with 'git pull' while on branch master    master  Tracked remote branches    master    ticgit除了对应的克隆地址外,它还给出了许多额外的信息。它友善地告诉你如果是在 master 分支,就可以用 git pull 命令抓取数据合并到本地。另外还列出了所有处于跟踪状态中的远端分支。

上面的例子非常简单,而随着使用 Git 的深入,git remote show 给出的信息可能会像这样:

$ git remote show origin* remote origin  URL: git@github.com:defunkt/github.git  Remote branch merged with 'git pull' while on branch issues    issues  Remote branch merged with 'git pull' while on branch master    master  New remote branches (next fetch will store in remotes/origin)    caching  Stale tracking branches (use 'git remote prune')    libwalker    walker2  Tracked remote branches    acl    apiv2    dashboard2    issues    master    postgres  Local branch pushed with 'git push'    master:master它告诉我们,运行 git push 时缺省推送的分支是什么(译注:最后两行)。它还显示了有哪些远端分支还没有同步到本地(译注:第六行的caching 分支),哪些已同步到本地的远端分支在远端服务器上已被删除(译注:Stale tracking branches 下面的两个分支),以及运行git pull 时将自动合并哪些分支(译注:前四行中列出的 issues 和 master 分支)。

远程仓库的删除和重命名
在新版 Git 中可以用 git remote rename 命令修改某个远程仓库在本地的简短名称,比如想把 pb 改成paul,可以这么运行:

$ git remote rename pb paul$ git remoteoriginpaul注意,对远程仓库的重命名,也会使对应的分支名称发生变化,原来的 pb/master 分支现在成了 paul/master。

碰到远端仓库服务器迁移,或者原来的克隆镜像不再使用,又或者某个参与者不再贡献代码,那么需要移除对应的远端仓库,可以运行 git remote rm 命令:

$ git remote rm paul$ git remoteorigin



2.6  打标签
同大多数 VCS 一样,Git 也可以对某一时间点上的版本打上标签。人们在发布某个软件版本(比如 v1.0 等等)的时候,经常这么做。本节我们一起来学习如何列出所有可用的标签,如何新建标签,以及各种不同类型标签之间的差别。

列显已有的标签
列出现有标签的命令非常简单,直接运行 git tag 即可:

$ git tagv0.1v1.3显示的标签按字母顺序排列,所以标签的先后并不表示重要程度的轻重。

我们可以用特定的搜索模式列出符合条件的标签。在 Git 自身项目仓库中,有着超过 240 个标签,如果你只对 1.4.2 系列的版本感兴趣,可以运行下面的命令:

$ git tag -l 'v1.4.2.*'v1.4.2.1v1.4.2.2v1.4.2.3v1.4.2.4新建标签
Git 使用的标签有两种类型:轻量级的(lightweight)和含附注的(annotated)。轻量级标签就像是个不会变化的分支,实际上它就是个指向特 定提交对象的引用。而含附注标签,实际上是存储在仓库中的一个独立对象,它有自身的校验和信息,包含着标签的名字,电子邮件地址和日期,以及标签说明,标 签本身也允许使用 GNU Privacy Guard (GPG) 来签署或验证。一般我们都建议使用含附注型的标签,以便保留相关信息;当然,如果只是临时性加注标签,或者不需要旁注额外信息,用轻量级标签也没问题。

含附注的标签
创建一个含附注类型的标签非常简单,用 -a (译注:取 annotated 的首字母)指定标签名字即可:

$ git tag -a v1.4 -m 'my version 1.4'$ git tagv0.1v1.3v1.4而 -m 选项则指定了对应的标签说明,Git 会将此说明一同保存在标签对象中。如果没有给出该选项,Git 会启动文本编辑软件供你输入标签说明。

可以使用 git show 命令查看相应标签的版本信息,并连同显示打标签时的提交对象。

$ git show v1.4 tag v1.4 Tagger: Scott Chacon Date: Mon Feb 9 14:45:11 2009 -0800 my version 1.4 commit 15027957951b64cf874c3557a0f3547bd83b3ff6 Merge: 4a447f7... a6b4c97... Author: Scott Chacon Date: Sun Feb 8 19:02:46 2009 -0800 Merge branch 'experiment'  我们可以看到在提交对象信息上面,列出了此标签的提交者和提交时间,以及相应的标签说明。

签署标签
如果你有自己的私钥,还可以用 GPG 来签署标签,只需要把之前的 -a 改为 -s (译注: 取 signed 的首字母)即可:

$ git tag -s v1.5 -m 'my signed 1.5 tag' You need a passphrase to unlock the secret key for user: "Scott Chacon " 1024-bit DSA key, ID F721C45A, created 2009-02-09现在再运行 git show 会看到对应的 GPG 签名也附在其内:

$ git show v1.5 tag v1.5 Tagger: Scott Chacon Date: Mon Feb 9 15:22:20 2009 -0800 my signed 1.5 tag -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.8 (Darwin) iEYEABECAAYFAkmQurIACgkQON3DxfchxFr5cACeIMN+ZxLKggJQf0QYiQBwgySN Ki0An2JeAVUCAiJ7Ox6ZEtK+NvZAj82/ =WryJ -----END PGP SIGNATURE----- commit 15027957951b64cf874c3557a0f3547bd83b3ff6 Merge: 4a447f7... a6b4c97... Author: Scott Chacon Date: Sun Feb 8 19:02:46 2009 -0800 Merge branch 'experiment'  稍后我们再学习如何验证已经签署的标签。

轻量级标签
轻量级标签实际上就是一个保存着对应提交对象的校验和信息的文件。要创建这样的标签,一个 -a,-s 或 -m 选项都不用,直接给出标签名字即可:

$ git tag v1.4-lw$ git tagv0.1v1.3v1.4v1.4-lwv1.5现在运行 git show 查看此标签信息,就只有相应的提交对象摘要:

$ git show v1.4-lw commit 15027957951b64cf874c3557a0f3547bd83b3ff6 Merge: 4a447f7... a6b4c97... Author: Scott Chacon Date: Sun Feb 8 19:02:46 2009 -0800 Merge branch 'experiment'验证标签
可以使用 git tag -v [tag-name] (译注:取 verify 的首字母)的方式验证已经签署的标签。此命令会调用 GPG 来验证签名,所以你需要有签署者的公钥,存放在 keyring 中,才能验证:

$ git tag -v v1.4.2.1 object 883653babd8ee7ea23e6a5c392bb739348b1eb61 type commit tag v1.4.2.1 tagger Junio C Hamano 1158138501 -0700 GIT 1.4.2.1 Minor fixes since 1.4.2, including git-mv and git-http with alternates. gpg: Signature made Wed Sep 13 02:08:25 2006 PDT using DSA key ID F3119B9A gpg: Good signature from "Junio C Hamano " gpg: aka "[jpeg image of size 1513]" Primary key fingerprint: 3565 2A26 2040 E066 C9A7 4A7D C0C6 D9A4 F311 9B9A  若是没有签署者的公钥,会报告类似下面这样的错误:

gpg: Signature made Wed Sep 13 02:08:25 2006 PDT using DSA key ID F3119B9Agpg: Can't check signature: public key not founderror: could not verify the tag 'v1.4.2.1'后期加注标签
你甚至可以在后期对早先的某次提交加注标签。比如在下面展示的提交历史中:

$ git log --pretty=oneline15027957951b64cf874c3557a0f3547bd83b3ff6 Merge branch 'experiment'a6b4c97498bd301d84096da251c98a07c7723e65 beginning write support0d52aaab4479697da7686c15f77a3d64d9165190 one more thing6d52a271eda8725415634dd79daabbc4d9b6008e Merge branch 'experiment'0b7434d86859cc7b8c3d5e1dddfed66ff742fcbc added a commit function4682c3261057305bdd616e23b64b0857d832627b added a todo file166ae0c4d3f420721acbb115cc33848dfcc2121a started write support9fceb02d0ae598e95dc970b74767f19372d61af8 updated rakefile964f16d36dfccde844893cac5b347e7b3d44abbc commit the todo8a5cbc430f1a9c3d00faaeffd07798508422908a updated readme我们忘了在提交 “updated rakefile” 后为此项目打上版本号 v1.2,没关系,现在也能做。只要在打标签的时候跟上对应提交对象的校验和(或前几位字符)即可:

$ git tag -a v1.2 9fceb02可以看到我们已经补上了标签:

$ git tag v0.1 v1.2 v1.3 v1.4 v1.4-lw v1.5 $ git show v1.2 tag v1.2 Tagger: Scott Chacon Date: Mon Feb 9 15:32:16 2009 -0800 version 1.2 commit 9fceb02d0ae598e95dc970b74767f19372d61af8 Author: Magnus Chacon Date: Sun Apr 27 20:43:35 2008 -0700 updated rakefile ...  分享标签
默认情况下,git push 并不会把标签传送到远端服务器上,只有通过显式命令才能分享标签到远端仓库。其命令格式如同推送分支,运行git push origin [tagname] 即可:

$ git push origin v1.5Counting objects: 50, done.Compressing objects: 100% (38/3, done.Writing objects: 100% (44/44), 4.56 KiB, done.Total 44 (delta 1, reused 8 (delta 1)To git@github.com:schacon/simplegit.git* [new tag]         v1.5 -> v1.5如果要一次推送所有本地新增的标签上去,可以使用 --tags 选项:

$ git push origin --tagsCounting objects: 50, done.Compressing objects: 100% (38/3, done.Writing objects: 100% (44/44), 4.56 KiB, done.Total 44 (delta 1, reused 8 (delta 1)To git@github.com:schacon/simplegit.git * [new tag]         v0.1 -> v0.1 * [new tag]         v1.2 -> v1.2 * [new tag]         v1.4 -> v1.4 * [new tag]         v1.4-lw -> v1.4-lw * [new tag]         v1.5 -> v1.5现在,其他人克隆共享仓库或拉取数据同步后,也会看到这些标签。



2.7  技巧和窍门
在结束本章之前,我还想和大家分享一些 Git 使用的技巧和窍门。很多使用 Git 的开发者可能根本就没用过这些技巧,我们也不是说在读过本书后非得用这些技巧不可,但至少应该有所了解吧。说实话,有了这些小窍门,我们的工作可以变得更简单,更轻松,更高效。

自动完成
如果你用的是 Bash shell,可以试试看 Git 提供的自动完成脚本。下载 Git 的源代码,进入 contrib/completion 目录,会看到一个git-completion.bash 文件。将此文件复制到你自己的用户主目录中(译注:按照下面的示例,还应改名加上点:cp git-completion.bash ~/.git-completion.bash),并把下面一行内容添加到你的.bashrc 文件中:

source ~/.git-completion.bash也可以为系统上所有用户都设置默认使用此脚本。Mac 上将此脚本复制到 /opt/local/etc/bash_completion.d 目录中,Linux 上则复制到/etc/bash_completion.d/ 目录中。这两处目录中的脚本,都会在 Bash 启动时自动加载。

如果在 Windows 上安装了 msysGit,默认使用的 Git Bash 就已经配好了这个自动完成脚本,可以直接使用。

在输入 Git 命令的时候可以敲两次跳格键(Tab),就会看到列出所有匹配的可用命令建议:

$ git co  commit config  此例中,键入 git co 然后连按两次 Tab 键,会看到两个相关的建议(命令) commit 和 config。继而输入 m 会自动完成git commit 命令的输入。

命令的选项也可以用这种方式自动完成,其实这种情况更实用些。比如运行 git log 的时候忘了相关选项的名字,可以输入开头的几个字母,然后敲 Tab 键看看有哪些匹配的:

$ git log --s --shortstat --since= --src-prefix= --stat --summary这个技巧不错吧,可以节省很多输入和查阅文档的时间。

Git 命令别名
Git 并不会推断你输入的几个字符将会是哪条命令,不过如果想偷懒,少敲几个命令的字符,可以用 git config 为命令设置别名。来看看下面的例子:

$ git config --global alias.co checkout$ git config --global alias.br branch$ git config --global alias.ci commit$ git config --global alias.st status现在,如果要输入 git commit 只需键入 git ci 即可。而随着 Git 使用的深入,会有很多经常要用到的命令,遇到这种情况,不妨建个别名提高效率。

使用这种技术还可以创造出新的命令,比方说取消暂存文件时的输入比较繁琐,可以自己设置一下:

$ git config --global alias.unstage 'reset HEAD --'这样一来,下面的两条命令完全等同:

$ git unstage fileA$ git reset HEAD fileA显然,使用别名的方式看起来更清楚。另外,我们还经常设置 last 命令:

$ git config --global alias.last 'log -1 HEAD'然后要看最后一次的提交信息,就变得简单多了:

$ git last commit 66938dae3329c7aebe598c2246a8e6af90d04646 Author: Josh Goebel Date: Tue Aug 26 19:48:51 2008 +0800 test for current head Signed-off-by: Scott Chacon  可以看出,实际上 Git 只是简单地在命令中替换了你设置的别名。不过有时候我们希望运行某个外部命令,而非 Git 的附属工具,这个好办,只需要在命令前加上 ! 就行。如果你自己写了些处理 Git 仓库信息的脚本的话,就可以用这种技术包装起来。作为演示,我们可以设置用 git visual 启动gitk:

$ git config --global alias.visual "!gitk"

2.8  小结
到目前为止,你已经学会了最基本的 Git 操作:创建和克隆仓库,做出更新,暂存并提交这些更新,以及查看所有历史更新记录。接下来,我们将学习 Git 的必杀技特性:分支模型。

作者: compare2000    时间: 2014-05-22 16:45
Git详解三: Git分支  
Git 分支
几乎每一种版本控制系统都以某种形式支持分支。使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作。在很多版本控制系统中,这是个昂贵的过程,常常需要创建一个源代码目录的完整副本,对大型项目来说会花费很长时间。

有人把 Git 的分支模型称为“必杀技特性”,而正是因为它,将 Git 从版本控制系统家族里区分出来。Git 有何特别之处呢?Git 的分支可谓是难以置信的轻量级,它的新建操作几乎可以在瞬间完成,并且在不同分支间切换起来也差不多一样快。和许多其他版本控制系统不同,Git 鼓励在工作流程中频繁使用分支与合并,哪怕一天之内进行许多次都没有关系。理解分支的概念并熟练运用后,你才会意识到为什么 Git 是一个如此强大而独特的工具,并从此真正改变你的开发方式。



3.1  何谓分支
为了理解 Git 分支的实现方式,我们需要回顾一下 Git 是如何储存数据的。或许你还记得第一章的内容,Git 保存的不是文件差异或者变化量,而只是一系列文件快照。

在 Git 中提交时,会保存一个提交(commit)对象,该对象包含一个指向暂存内容快照的指针,包含本次提交的作者等相关附属信息,包含零个或多个指向该提交对 象的父对象指针:首次提交是没有直接祖先的,普通提交有一个祖先,由两个或多个分支合并产生的提交则有多个祖先。

为直观起见,我们假设在工作目录中有三个文件,准备将它们暂存后提交。暂存操作会对每一个文件计算校验和(即第一章中提到的 SHA-1 哈希字串),然后把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 类型的对象存储这些快照),并将校验和加入暂存区域:

$ git add README test.rb LICENSE$ git commit -m 'initial commit of my project'当使用 git commit 新建一个提交对象前,Git 会先计算每一个子目录(本例中就是项目根目录)的校验和,然后在 Git 仓库中将这些目录保存为树(tree)对象。之后 Git 创建的提交对象,除了包含相关提交信息以外,还包含着指向这个树对象(项目根目录)的指针,如此它就可以在将来需要的时候,重现此次快照的内容了。

现在,Git 仓库中有五个对象:三个表示文件快照内容的 blob 对象;一个记录着目录树内容及其中各个文件对应 blob 对象索引的 tree 对象;以及一个包含指向 tree 对象(根目录)的索引和其他提交信息元数据的 commit 对象。概念上来说,仓库中的各个对象保存的数据和相互关系看起来如图 3-1 所示:



图 3-1. 单个提交对象在仓库中的数据结构
作些修改后再次提交,那么这次的提交对象会包含一个指向上次提交对象的指针(译注:即下图中的 parent 对象)。两次提交后,仓库历史会变成图 3-2 的样子:



图 3-2. 多个提交对象之间的链接关系
现在来谈分支。Git 中的分支,其实本质上仅仅是个指向 commit 对象的可变指针。Git 会使用 master 作为分支的默认名字。在若干次提交后,你其实已经有了一个指向最后一次提交对象的 master 分支,它在每次提交的时候都会自动向前移动。



图 3-3. 分支其实就是从某个提交对象往回看的历史
那么,Git 又是如何创建一个新的分支的呢?答案很简单,创建一个新的分支指针。比如新建一个 testing 分支,可以使用 git branch 命令:

$ git branch testing这会在当前 commit 对象上新建一个分支指针(见图 3-4)。



图 3-4. 多个分支指向提交数据的历史
那么,Git 是如何知道你当前在哪个分支上工作的呢?其实答案也很简单,它保存着一个名为 HEAD 的特别指针。请注意它和你熟知的许多其他版本控制系统(比如 Subversion 或 CVS)里的 HEAD 概念大不相同。在 Git 中,它是一个指向你正在工作中的本地分支的指针(译注:将 HEAD 想象为当前分支的别名。)。运行git branch 命令,仅仅是建立了一个新的分支,但不会自动切换到这个分支中去,所以在这个例子中,我们依然还在 master 分支里工作(参考图 3-5)。



图 3-5. HEAD 指向当前所在的分支
要切换到其他分支,可以执行 git checkout 命令。我们现在转换到新建的 testing 分支:

$ git checkout testing这样 HEAD 就指向了 testing 分支(见图3-6)。



图 3-6. HEAD 在你转换分支时指向新的分支
这样的实现方式会给我们带来什么好处呢?好吧,现在不妨再提交一次:

$ vim test.rb$ git commit -a -m 'made a change'图 3-7 展示了提交后的结果。



图 3-7. 每次提交后 HEAD 随着分支一起向前移动
非常有趣,现在 testing 分支向前移动了一格,而 master 分支仍然指向原先 git checkout 时所在的 commit 对象。现在我们回到 master 分支看看:

$ git checkout master图 3-8 显示了结果。



图 3-8. HEAD 在一次 checkout 之后移动到了另一个分支
这条命令做了两件事。它把 HEAD 指针移回到 master 分支,并把工作目录中的文件换成了 master 分支所指向的快照内容。也就是说,现在开始所做的改动,将始于本项目中一个较老的版本。它的主要作用是将 testing 分支里作出的修改暂时取消,这样你就可以向另一个方向进行开发。

我们作些修改后再次提交:

$ vim test.rb$ git commit -a -m 'made other changes'现在我们的项目提交历史产生了分叉(如图 3-9 所示),因为刚才我们创建了一个分支,转换到其中进行了一些工作,然后又回到原来的主分支进行了另外一些工作。这些改变分别孤立在不同的分支里:我们可以 在不同分支里反复切换,并在时机成熟时把它们合并到一起。而所有这些工作,仅仅需要branch 和 checkout 这两条命令就可以完成。



图 3-9. 不同流向的分支历史
由于 Git 中的分支实际上仅是一个包含所指对象校验和(40 个字符长度 SHA-1 字串)的文件,所以创建和销毁一个分支就变得非常廉价。说白了,新建一个分支就是向一个文件写入 41 个字节(外加一个换行符)那么简单,当然也就很快了。

这和大多数版本控制系统形成了鲜明对比,它们管理分支大多采取备份所有项目文件到特定目录的方式,所以根据项目文件数量和大小不同,可能花费的时间 也会有相当大的差别,快则几秒,慢则数分钟。而 Git 的实现与项目复杂度无关,它永远可以在几毫秒的时间内完成分支的创建和切换。同时,因为每次提交时都记录了祖先信息(译注:即parent 对象),将来要合并分支时,寻找恰当的合并基础(译注:即共同祖先)的工作其实已经自然而然地摆在那里了,所以实现起来非常容易。Git 鼓励开发者频繁使用分支,正是因为有着这些特性作保障。

接下来看看,我们为什么应该频繁使用分支。



3.2  分支的新建与合并
现在让我们来看一个简单的分支与合并的例子,实际工作中大体也会用到这样的工作流程:

1. 开发某个网站。

2. 为实现某个新的需求,创建一个分支。

3. 在这个分支上开展工作。

假设此时,你突然接到一个电话说有个很严重的问题需要紧急修补,那么可以按照下面的方式处理:

1. 返回到原先已经发布到生产服务器上的分支。

2. 为这次紧急修补建立一个新分支,并在其中修复问题。

3. 通过测试后,回到生产服务器所在的分支,将修补分支合并进来,然后再推送到生产服务器上。

4. 切换到之前实现新需求的分支,继续工作。

分支的新建与切换
首先,我们假设你正在项目中愉快地工作,并且已经提交了几次更新(见图 3-10)。



图 3-10. 一个简短的提交历史
现在,你决定要修补问题追踪系统上的 #53 问题。顺带说明下,Git 并不同任何特定的问题追踪系统打交道。这里为了说明要解决的问题,才把新建的分支取名为 iss53。要新建并切换到该分支,运行git checkout 并加上 -b 参数:

$ git checkout -b iss53Switched to a new branch "iss53"这相当于执行下面这两条命令:

$ git branch iss53$ git checkout iss53图 3-11 示意该命令的执行结果。



图 3-11. 创建了一个新分支的指针
接着你开始尝试修复问题,在提交了若干次更新后,iss53 分支的指针也会随着向前推进,因为它就是当前分支(换句话说,当前的 HEAD 指针正指向 iss53,见图 3-12):

$ vim index.html$ git commit -a -m 'added a new footer [issue 53]'

图 3-12. iss53 分支随工作进展向前推进
现在你就接到了那个网站问题的紧急电话,需要马上修补。有了 Git ,我们就不需要同时发布这个补丁和 iss53 里作出的修改,也不需要在创建和发布该补丁到服务器之前花费大力气来复原这些修改。唯一需要的仅仅是切换回master 分支。

不过在此之前,留心你的暂存区或者工作目录里,那些还没有提交的修改,它会和你即将检出的分支产生冲突从而阻止 Git 为你切换分支。切换分支的时候最好保持一个清洁的工作区域。稍后会介绍几个绕过这种问题的办法(分别叫做 stashing 和 commit amending)。目前已经提交了所有的修改,所以接下来可以正常转换到master 分支:

$ git checkout masterSwitched to branch "master"此时工作目录中的内容和你在解决问题 #53 之前一模一样,你可以集中精力进行紧急修补。这一点值得牢记:Git 会把工作目录的内容恢复为检出某分支时它所指向的那个提交对象的快照。它会自动添加、删除和修改文件以确保目录的内容和你当时提交时完全一样。

接下来,你得进行紧急修补。我们创建一个紧急修补分支 hotfix 来开展工作,直到搞定(见图 3-13):

$ git checkout -b 'hotfix'Switched to a new branch "hotfix"$ vim index.html$ git commit -a -m 'fixed the broken email address'[hotfix]: created 3a0874c: "fixed the broken email address" 1 files changed, 0 insertions(+), 1 deletions(-)

图 3-13. hotfix 分支是从 master 分支所在点分化出来的
有必要作些测试,确保修补是成功的,然后回到 master 分支并把它合并进来,然后发布到生产服务器。用 git merge 命令来进行合并:

$ git checkout master$ git merge hotfixUpdating f42c576..3a0874cFast forward README |    1 - 1 files changed, 0 insertions(+), 1 deletions(-)请注意,合并时出现了“Fast forward”的提示。由于当前 master 分支所在的提交对象是要并入的 hotfix 分支的直接上游,Git 只需把master 分支指针直接右移。换句话说,如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,因为这种单线的历史分支不存在任何需要解决的分歧,所以这种合并过程可以称为快进(Fast forward)。

现在最新的修改已经在当前 master 分支所指向的提交对象中了,可以部署到生产服务器上去了(见图 3-14)。



图 3-14. 合并之后,master 分支和 hotfix 分支指向同一位置。
在那个超级重要的修补发布以后,你想要回到被打扰之前的工作。由于当前 hotfix 分支和 master 都指向相同的提交对象,所以hotfix 已经完成了历史使命,可以删掉了。使用 git branch 的 -d 选项执行删除操作:

$ git branch -d hotfixDeleted branch hotfix (3a0874c).现在回到之前未完成的 #53 问题修复分支上继续工作(图 3-15):

$ git checkout iss53Switched to branch "iss53"$ vim index.html$ git commit -a -m 'finished the new footer [issue 53]'[iss53]: created ad82d7a: "finished the new footer [issue 53]" 1 files changed, 1 insertions(+), 0 deletions(-)

图 3-15. iss53 分支可以不受影响继续推进。
不用担心之前 hotfix 分支的修改内容尚未包含到 iss53 中来。如果确实需要纳入此次修补,可以用git merge master 把 master 分支合并到 iss53;或者等 iss53 完成之后,再将iss53 分支中的更新并入 master。

分支的合并
在问题 #53 相关的工作完成之后,可以合并回 master 分支。实际操作同前面合并 hotfix 分支差不多,只需回到master 分支,运行 git merge 命令指定要合并进来的分支:

$ git checkout master$ git merge iss53Merge made by recursive. README |    1 + 1 files changed, 1 insertions(+), 0 deletions(-)请注意,这次合并操作的底层实现,并不同于之前 hotfix 的并入方式。因为这次你的开发历史是从更早的地方开始分叉的。由于当前 master 分支所指向的提交对象(C4)并不是 iss53 分支的直接祖先,Git 不得不进行一些额外处理。就此例而言,Git 会用两个分支的末端(C4 和 C5)以及它们的共同祖先(C2)进行一次简单的三方合并计算。图 3-16 用红框标出了 Git 用于合并的三个提交对象:



图 3-16. Git 为分支合并自动识别出最佳的同源合并点。
这次,Git 没有简单地把分支指针右移,而是对三方合并后的结果重新做一个新的快照,并自动创建一个指向它的提交对象(C6)(见图 3-17)。这个提交对象比较特殊,它有两个祖先(C4 和 C5)。

值得一提的是 Git 可以自己裁决哪个共同祖先才是最佳合并基础;这和 CVS 或 Subversion(1.5 以后的版本)不同,它们需要开发者手工指定合并基础。所以此特性让 Git 的合并操作比其他系统都要简单不少。



图 3-17. Git 自动创建了一个包含了合并结果的提交对象。
既然之前的工作成果已经合并到 master 了,那么 iss53 也就没用了。你可以就此删除它,并在问题追踪系统里关闭该问题。

$ git branch -d iss53遇到冲突时的分支合并
有时候合并操作并不会如此顺利。如果在不同的分支中都修改了同一个文件的同一部分,Git 就无法干净地把两者合到一起(译注:逻辑上说,这种问题只能由人来裁决。)。如果你在解决问题 #53 的过程中修改了hotfix 中修改的部分,将得到类似下面的结果:

$ git merge iss53Auto-merging index.htmlCONFLICT (content):Merge conflict in index.htmlAutomatic merge failed; fix conflicts and then commit the result.Git 作了合并,但没有提交,它会停下来等你解决冲突。要看看哪些文件在合并时发生冲突,可以用 git status 查阅:

[master*]$ git status index.html: needs merge # On branch master # Changed but not updated: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # unmerged: index.html #  任何包含未解决冲突的文件都会以未合并(unmerged)的状态列出。Git 会在有冲突的文件里加入标准的冲突解决标记,可以通过它们来手工定位并解决这些冲突。可以看到此文件包含类似下面这样的部分:

<<<<<<< HEAD:index.html contact : email.support@github.com
======= please contact us at support@github.com
>>>>>>> iss53:index.html可以看到 ======= 隔开的上半部分,是 HEAD(即 master 分支,在运行merge 命令时所切换到的分支)中的内容,下半部分是在 iss53 分支中的内容。解决冲突的办法无非是二者选其一或者由你亲自整合到一起。比如你可以通过把这段内容替换为下面这样来解决:

please contact us at email.support@github.com
这个解决方案各采纳了两个分支中的一部分内容,而且我还删除了 <<<<<<<,======= 和 >>>>>>> 这些行。在解决了所有文件里的所有冲突后,运行 git add 将把它们标记为已解决状态(译注:实际上就是来一次快照保存到暂存区域。)。因为一旦暂存,就表示冲突已经解决。如果你想用一个有图形界面的工具来解决这些问题,不妨运行git mergetool,它会调用一个可视化的合并工具并引导你解决所有冲突:

$ git mergetoolmerge tool candidates: kdiff3 tkdiff xxdiff meld gvimdiff opendiff emerge vimdiffMerging the files: index.htmlNormal merge conflict for 'index.html':  {local}: modified  {remote}: modifiedHit return to start merge resolution tool (opendiff):如果不想用默认的合并工具(Git 为我默认选择了 opendiff,因为我在 Mac 上运行了该命令),你可以在上方”merge tool candidates”里找到可用的合并工具列表,输入你想用的工具名。我们将在第七章讨论怎样改变环境中的默认值。

退出合并工具以后,Git 会询问你合并是否成功。如果回答是,它会为你把相关文件暂存起来,以表明状态为已解决。

再运行一次 git status 来确认所有冲突都已解决:

$ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # modified: index.html #如果觉得满意了,并且确认所有冲突都已解决,也就是进入了暂存区,就可以用 git commit 来完成这次合并提交。提交的记录差不多是这样:

Merge branch 'iss53'Conflicts:  index.html## It looks like you may be committing a MERGE.# If this is not correct, please remove the file# .git/MERGE_HEAD# and try again.#如果想给将来看这次合并的人一些方便,可以修改该信息,提供更多合并细节。比如你都作了哪些改动,以及这么做的原因。有时候裁决冲突的理由并不直接或明显,有必要略加注解。



3.3  分支的管理
到目前为止,你已经学会了如何创建、合并和删除分支。除此之外,我们还需要学习如何管理分支,在日后的常规工作中会经常用到下面介绍的管理命令。

git branch 命令不仅仅能创建和删除分支,如果不加任何参数,它会给出当前所有分支的清单:

$ git branch  iss53* master  testing注意看 master 分支前的 * 字符:它表示当前所在的分支。也就是说,如果现在提交更新,master 分支将随着开发进度前移。若要查看各个分支最后一个提交对象的信息,运行git branch -v:

$ git branch -v  iss53   93b412c fix javascript issue* master  7a98805 Merge branch 'iss53'  testing 782fd34 add scott to the author list in the readmes要从该清单中筛选出你已经(或尚未)与当前分支合并的分支,可以用 --merge 和 --no-merged 选项(Git 1.5.6 以上版本)。比如用git branch --merge 查看哪些分支已被并入当前分支(译注:也就是说哪些分支是当前分支的直接上游。):

$ git branch --merged  iss53* master之前我们已经合并了 iss53,所以在这里会看到它。一般来说,列表中没有 * 的分支通常都可以用 git branch -d 来删掉。原因很简单,既然已经把它们所包含的工作整合到了其他分支,删掉也不会损失什么。

另外可以用 git branch --no-merged 查看尚未合并的工作:

$ git branch --no-merged  testing它会显示还未合并进来的分支。由于这些分支中还包含着尚未合并进来的工作成果,所以简单地用 git branch -d 删除该分支会提示错误,因为那样做会丢失数据:

$ git branch -d testingerror: The branch 'testing' is not an ancestor of your current HEAD.If you are sure you want to delete it, run 'git branch -D testing'.不过,如果你确实想要删除该分支上的改动,可以用大写的删除选项 -D 强制执行,就像上面提示信息中给出的那样。

3.4  利用分支进行开发的工作流程
现在我们已经学会了新建分支和合并分支,可以(或应该)用它来做点什么呢?在本节,我们会介绍一些利用分支进行开发的工作流程。而正是由于分支管理的便捷,才衍生出了这类典型的工作模式,你可以根据项目的实际情况选择一种用用看。

长期分支
由于 Git 使用简单的三方合并,所以就算在较长一段时间内,反复多次把某个分支合并到另一分支,也不是什么难事。也就是说,你可以同时拥有多个开放的分支,每个分支用于完成特定的任务,随着开发的推进,你可以随时把某个特性分支的成果并到其他分支中。

许多使用 Git 的开发者都喜欢用这种方式来开展工作,比如仅在 master 分支中保留完全稳定的代码,即已经发布或即将发布的代码。与此同时,他们还有一个名为develop 或 next 的平行分支,专门用于后续的开发,或仅用于稳定性测试 — 当然并不是说一定要绝对稳定,不过一旦进入某种稳定状态,便可以把它合并到master 里。这样,在确保这些已完成的特性分支(短期分支,比如之前的 iss53 分支)能够通过所有测试,并且不会引入更多错误之后,就可以并到主干分支中,等待下一次的发布。

本质上我们刚才谈论的,是随着提交对象不断右移的指针。稳定分支的指针总是在提交历史中落后一大截,而前沿分支总是比较靠前(见图 3-18)。



图 3-18. 稳定分支总是比较老旧。
或者把它们想象成工作流水线,或许更好理解一些,经过测试的提交对象集合被遴选到更稳定的流水线(见图 3-19)。



图 3-19. 想象成流水线可能会容易点。
你可以用这招维护不同层次的稳定性。某些大项目还会有个 proposed(建议)或 pu(proposed updates,建议更新)分支,它包含着那些可能还没有成熟到进入next 或 master 的内容。这么做的目的是拥有不同层次的稳定性:当这些分支进入到更稳定的水平时,再把它们合并到更高层分支中去。再次说明下,使用多个长期分支的做法并非必需,不过一般来说,对于特大型项目或特复杂的项目,这么做确实更容易管理。

特性分支
在任何规模的项目中都可以使用特性(Topic)分支。一个特性分支是指一个短期的,用来实现单一特性或与其相关工作的分支。可能你在以前的版本控 制系统里从未做过类似这样的事情,因为通常创建与合并分支消耗太大。然而在 Git 中,一天之内建立、使用、合并再删除多个分支是常见的事。

我们在上节的例子里已经见过这种用法了。我们创建了 iss53 和 hotfix 这两个特性分支,在提交了若干更新后,把它们合并到主干分支,然后删除。该技术允许你迅速且完全的进行语境切换 — 因为你的工作分散在不同的流水线里,每个分支里的改变都和它的目标特性相关,浏览代码之类的事情因而变得更简单了。你可以把作出的改变保持在特性分支中几 分钟,几天甚至几个月,等它们成熟以后再合并,而不用在乎它们建立的顺序或者进度。

现在我们来看一个实际的例子。请看图 3-20,由下往上,起先我们在 master 工作到 C1,然后开始一个新分支 iss91 尝试修复 91 号缺陷,提交到 C6 的时候,又冒出一个解决该问题的新办法,于是从之前 C4 的地方又分出一个分支iss91v2,干到 C8 的时候,又回到主干 master 中提交了 C9 和 C10,再回到 iss91v2 继续工作,提交 C11,接着,又冒出个不太确定的想法,从 master 的最新提交 C10 处开了个新的分支dumbidea 做些试验。



图 3-20. 拥有多个特性分支的提交历史。
现在,假定两件事情:我们最终决定使用第二个解决方案,即 iss91v2 中的办法;另外,我们把 dumbidea 分支拿给同事们看了以后,发现它竟然是个天才之作。所以接下来,我们准备抛弃原来的iss91 分支(实际上会丢弃 C5 和 C6),直接在主干中并入另外两个分支。最终的提交历史将变成图 3-21 这样:



图 3-21. 合并了 dumbidea 和 iss91v2 后的分支历史。
请务必牢记这些分支全部都是本地分支,这一点很重要。当你在使用分支及合并的时候,一切都是在你自己的 Git 仓库中进行的 — 完全不涉及与服务器的交互。



3.5  远程分支
远程分支(remote branch)是对远程仓库中的分支的索引。它们是一些无法移动的本地分支;只有在 Git 进行网络交互时才会更新。远程分支就像是书签,提醒着你上次连接远程仓库时上面各分支的位置。

我们用 (远程仓库名)/(分支名) 这样的形式表示远程分支。比如我们想看看上次同 origin 仓库通讯时master 的样子,就应该查看 origin/master 分支。如果你和同伴一起修复某个问题,但他们先推送了一个iss53 分支到远程仓库,虽然你可能也有一个本地的 iss53 分支,但指向服务器上最新更新的却应该是 origin/iss53 分支。

可能有点乱,我们不妨举例说明。假设你们团队有个地址为 git.ourcompany.com 的 Git 服务器。如果你从这里克隆,Git 会自动为你将此远程仓库命名为origin,并下载其中所有的数据,建立一个指向它的 master 分支的指针,在本地命名为 origin/master,但你无法在本地更改其数据。接着,Git 建立一个属于你自己的本地master 分支,始于 origin 上 master 分支相同的位置,你可以就此开始工作(见图 3-22):



图 3-22. 一次 Git 克隆会建立你自己的本地分支 master 和远程分支 origin/master,它们都指向 origin/master 分支的最后一次提交。
如果你在本地 master 分支做了些改动,与此同时,其他人向 git.ourcompany.com 推送了他们的更新,那么服务器上的master 分支就会向前推进,而于此同时,你在本地的提交历史正朝向不同方向发展。不过只要你不和服务器通讯,你的 origin/master 指针仍然保持原位不会移动(见图 3-23)。



图 3-23. 在本地工作的同时有人向远程仓库推送内容会让提交历史开始分流。
可以运行 git fetch origin 来同步远程服务器上的数据到本地。该命令首先找到 origin 是哪个服务器(本例为git.ourcompany.com),从上面获取你尚未拥有的数据,更新你本地的数据库,然后把 origin/master 的指针移到它最新的位置上(见图 3-24)。



图 3-24. git fetch 命令会更新 remote 索引。
为了演示拥有多个远程分支(在不同的远程服务器上)的项目是如何工作的,我们假设你还有另一个仅供你的敏捷开发小组使用的内部服务器 git.team1.ourcompany.com。可以用第二章中提到的git remote add 命令把它加为当前项目的远程分支之一。我们把它命名为 teamone,以便代替原始的 Git 地址(见图 3-25)。



图 3-25. 把另一个服务器加为远程仓库
现在你可以用 git fetch teamone 来获取小组服务器上你还没有的数据了。由于当前该服务器上的内容是你 origin 服务器上的子集,Git 不会下载任何数据,而只是简单地创建一个名为teamone/master 的分支,指向 teamone 服务器上 master 分支所在的提交对象31b8e(见图 3-26)。



图 3-26. 你在本地有了一个指向 teamone 服务器上 master 分支的索引。
推送本地分支
要想和其他人分享某个本地分支,你需要把它推送到一个你拥有写权限的远程仓库。你的本地分支不会被自动同步到你引入的远程服务器上,除非你明确执行推送操作。换句话说,对于无意分享的分支,你尽管保留为私人分支好了,而只推送那些协同工作要用到的特性分支。

如果你有个叫 serverfix 的分支需要和他人一起开发,可以运行 git push (远程仓库名) (分支名):

$ git push origin serverfixCounting objects: 20, done.Compressing objects: 100% (14/14), done.Writing objects: 100% (15/15), 1.74 KiB, done.Total 15 (delta 5), reused 0 (delta 0)To git@github.com:schacon/simplegit.git * [new branch]      serverfix -> serverfix这其实有点像条捷径。Git 自动把 serverfix 分支名扩展为 refs/heads/serverfix:refs/heads/serverfix,意为“取出我在本地的 serverfix 分支,推送到远程仓库的 serverfix 分支中去”。我们将在第九章进一步介绍refs/heads/ 部分的细节,不过一般使用的时候都可以省略它。也可以运行 git push origin serverfix:serferfix 来实现相同的效果,它的意思是“上传我本地的 serverfix 分支到远程仓库中去,仍旧称它为 serverfix 分支”。通过此语法,你可以把本地分支推送到某个命名不同的远程分支:若想把远程分支叫作awesomebranch,可以用 git push origin serverfix:awesomebranch 来推送数据。

接下来,当你的协作者再次从服务器上获取数据时,他们将得到一个新的远程分支 origin/serverfix:

$ git fetch originremote: Counting objects: 20, done.remote: Compressing objects: 100% (14/14), done.remote: Total 15 (delta 5), reused 0 (delta 0)Unpacking objects: 100% (15/15), done.From git@github.com:schacon/simplegit * [new branch]      serverfix    -> origin/serverfix值得注意的是,在 fetch 操作下载好新的远程分支之后,你仍然无法在本地编辑该远程仓库中的分支。换句话说,在本例中,你不会有一个新的serverfix 分支,有的只是一个你无法移动的 origin/serverfix 指针。

如果要把该内容合并到当前分支,可以运行 git merge origin/serverfix。如果想要一份自己的 serverfix 来开发,可以在远程分支的基础上分化出一个新的分支来:

$ git checkout -b serverfix origin/serverfixBranch serverfix set up to track remote branch refs/remotes/origin/serverfix.Switched to a new branch "serverfix"这会切换到新建的 serverfix 本地分支,其内容同远程分支 origin/serverfix 一致,这样你就可以在里面继续开发了。

跟踪远程分支
从远程分支 checkout 出来的本地分支,称为_跟踪分支(tracking branch)_。跟踪分支是一种和远程分支有直接联系的本地分支。在跟踪分支里输入git push,Git 会自行推断应该向哪个服务器的哪个分支推送数据。反过来,在这些分支里运行 git pull 会获取所有远程索引,并把它们的数据都合并到本地分支中来。

在克隆仓库时,Git 通常会自动创建一个名为 master 的分支来跟踪 origin/master。这正是git push 和 git pull 一开始就能正常工作的原因。当然,你可以随心所欲地设定为其它跟踪分支,比如origin 上除了 master 之外的其它分支。刚才我们已经看到了这样的一个例子:git checkout -b [分支名] [远程名]/[分支名]。如果你有 1.6.2 以上版本的 Git,还可以用--track 选项简化:

$ git checkout --track origin/serverfixBranch serverfix set up to track remote branch refs/remotes/origin/serverfix.Switched to a new branch "serverfix"要为本地分支设定不同于远程分支的名字,只需在前个版本的命令里换个名字:

$ git checkout -b sf origin/serverfixBranch sf set up to track remote branch refs/remotes/origin/serverfix.Switched to a new branch "sf"现在你的本地分支 sf 会自动向 origin/serverfix 推送和抓取数据了。

删除远程分支
如果不再需要某个远程分支了,比如搞定了某个特性并把它合并进了远程的 master 分支(或任何其他存放稳定代码的地方),可以用这个非常无厘头的语法来删除它:git push [远程名] :[分支名]。如果想在服务器上删除serverfix 分支,运行下面的命令:

$ git push origin :serverfixTo git@github.com:schacon/simplegit.git - [deleted]         serverfix咚!服务器上的分支没了。你最好特别留心这一页,因为你一定会用到那个命令,而且你很可能会忘掉它的语法。有种方便记忆这条命令的方法:记住我们不久前见过的 git push [远程名] [本地分支]:[远程分支] 语法,如果省略 [本地分支],那就等于是在说“在这里提取空白然后把它变成[远程分支]”。



3.6  分支的衍合
把一个分支整合到另一个分支的办法有两种:merge 和 rebase(译注:rebase 的翻译暂定为“衍合”,大家知道就可以了。)。在本章我们会学习什么是衍合,如何使用衍合,为什么衍合操作如此富有魅力,以及我们应该在什么情况下使用衍合。

基本的衍合操作
请回顾之前有关合并的一节(见图 3-27),你会看到开发进程分叉到两个不同分支,又各自提交了更新。



图 3-27. 最初分叉的提交历史。
之前介绍过,最容易的整合分支的方法是 merge 命令,它会把两个分支最新的快照(C3 和 C4)以及二者最新的共同祖先(C2)进行三方合并,合并的结果是产生一个新的提交对象(C5)。如图 3-28 所示:



图 3-28. 通过合并一个分支来整合分叉了的历史。
其实,还有另外一个选择:你可以把在 C3 里产生的变化补丁在 C4 的基础上重新打一遍。在 Git 里,这种操作叫做_衍合(rebase)_。有了 rebase 命令,就可以把在一个分支里提交的改变移到另一个分支里重放一遍。

在上面这个例子中,运行:

$ git checkout experiment$ git rebase masterFirst, rewinding head to replay your work on top of it...Applying: added staged command它的原理是回到两个分支最近的共同祖先,根据当前分支(也就是要进行衍合的分支 experiment)后续的历次提交对象(这里只有一个 C3),生成一系列文件补丁,然后以基底分支(也就是主干分支master)最后一个提交对象(C4)为新的出发点,逐个应用之前准备好的补丁文件,最后会生成一个新的合并提交对象(C3’),从而改写 experiment 的提交历史,使它成为 master 分支的直接下游,如图 3-29 所示:



图 3-29. 把 C3 里产生的改变到 C4 上重演一遍。
现在回到 master 分支,进行一次快进合并(见图 3-30):



图 3-30. master 分支的快进。
现在的 C3’ 对应的快照,其实和普通的三方合并,即上个例子中的 C5 对应的快照内容一模一样了。虽然最后整合得到的结果没有任何区别,但衍合能产生一个更为整洁的提交历史。如果视察一个衍合过的分支的历史记录,看起来会更 清楚:仿佛所有修改都是在一根线上先后进行的,尽管实际上它们原本是同时并行发生的。

一般我们使用衍合的目的,是想要得到一个能在远程分支上干净应用的补丁 — 比如某些项目你不是维护者,但想帮点忙的话,最好用衍合:先在自己的一个分支里进行开发,当准备向主项目提交补丁的时候,根据最新的origin/master 进行一次衍合操作然后再提交,这样维护者就不需要做任何整合工作(译注:实际上是把解决分支补丁同最新主干代码之间冲突的责任,化转为由提交补丁的人来解决。),只需根据你提供的仓库地址作一次快进合并,或者直接采纳你提交的补丁。

请注意,合并结果中最后一次提交所指向的快照,无论是通过衍合,还是三方合并,都会得到相同的快照内容,只不过提交历史不同罢了。衍合是按照每行的修改次序重演一遍修改,而合并是把最终结果合在一起。

有趣的衍合
衍合也可以放到其他分支进行,并不一定非得根据分化之前的分支。以图 3-31 的历史为例,我们为了给服务器端代码添加一些功能而创建了特性分支 server,然后提交 C3 和 C4。然后又从 C3 的地方再增加一个client 分支来对客户端代码进行一些相应修改,所以提交了 C8 和 C9。最后,又回到 server 分支提交了 C10。



图 3-31. 从一个特性分支里再分出一个特性分支的历史。
假设在接下来的一次软件发布中,我们决定先把客户端的修改并到主线中,而暂缓并入服务端软件的修改(因为还需要进一步测试)。这个时候,我们就可以把基于 server 分支而非 master 分支的改变(即 C8 和 C9),跳过 server 直接放到master 分支中重演一遍,但这需要用 git rebase 的 --onto 选项指定新的基底分支master:

$ git rebase --onto master server client这好比在说:“取出 client 分支,找出 client 分支和 server 分支的共同祖先之后的变化,然后把它们在master 上重演一遍”。是不是有点复杂?不过它的结果如图 3-32 所示,非常酷(译注:虽然 client 里的 C8, C9 在 C3 之后,但这仅表明时间上的先后,而非在 C3 修改的基础上进一步改动,因为server 和 client 这两个分支对应的代码应该是两套文件,虽然这么说不是很严格,但应理解为在 C3 时间点之后,对另外的文件所做的 C8,C9 修改,放到主干重演。):



图 3-32. 将特性分支上的另一个特性分支衍合到其他分支。
现在可以快进 master 分支了(见图 3-33):

$ git checkout master$ git merge client

图 3-33. 快进 master 分支,使之包含 client 分支的变化。
现在我们决定把 server 分支的变化也包含进来。我们可以直接把 server 分支衍合到 master,而不用手工切换到 server 分支后再执行衍合操作 — git rebase [主分支] [特性分支] 命令会先取出特性分支server,然后在主分支 master 上重演:

$ git rebase master server于是,server 的进度应用到 master 的基础上,如图 3-34 所示:



图 3-34. 在 master 分支上衍合 server 分支。
然后就可以快进主干分支 master 了:

$ git checkout master$ git merge server现在 client 和 server 分支的变化都已经集成到主干分支来了,可以删掉它们了。最终我们的提交历史会变成图 3-35 的样子:

$ git branch -d client$ git branch -d server

图 3-35. 最终的提交历史
衍合的风险
呃,奇妙的衍合也并非完美无缺,要用它得遵守一条准则:

一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行衍合操作。

如果你遵循这条金科玉律,就不会出差错。否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。

在进行衍合的时候,实际上抛弃了一些现存的提交对象而创造了一些类似但不同的新的提交对象。如果你把原来分支中的提交对象发布出去,并且其他人更新下载后在其基础上开展工作,而稍后你又用git rebase 抛弃这些提交对象,把新的重演后的提交对象发布出去的话,你的合作者就不得不重新合并他们的工作,这样当你再次从他们那里获取内容时,提交历史就会变得一团糟。

下面我们用一个实际例子来说明为什么公开的衍合会带来问题。假设你从一个中央服务器克隆然后在它的基础上搞了一些开发,提交历史类似图 3-36 所示:



图 3-36. 克隆一个仓库,在其基础上工作一番。
现在,某人在 C1 的基础上做了些改变,并合并他自己的分支得到结果 C6,推送到中央服务器。当你抓取并合并这些数据到你本地的开发分支中后,会得到合并结果 C7,历史提交会变成图 3-37 这样:



图 3-37. 抓取他人提交,并入自己主干。
接下来,那个推送 C6 上来的人决定用衍合取代之前的合并操作;继而又用 git push --force 覆盖了服务器上的历史,得到 C4’。而之后当你再从服务器上下载最新提交后,会得到:



图 3-38. 有人推送了衍合后得到的 C4’,丢弃了你作为开发基础的 C4 和 C6。
下载更新后需要合并,但此时衍合产生的提交对象 C4’ 的 SHA-1 校验值和之前 C4 完全不同,所以 Git 会把它们当作新的提交对象处理,而实际上此刻你的提交历史 C7 中早已经包含了 C4 的修改内容,于是合并操作会把 C7 和 C4’ 合并为 C8(见图 3-39):



图 3-39. 你把相同的内容又合并了一遍,生成一个新的提交 C8。
C8 这一步的合并是迟早会发生的,因为只有这样你才能和其他协作者提交的内容保持同步。而在 C8 之后,你的提交历史里就会同时包含 C4 和 C4’,两者有着不同的 SHA-1 校验值,如果用git log 查看历史,会看到两个提交拥有相同的作者日期与说明,令人费解。而更糟的是,当你把这样的历史推送到服务器后,会再次把这些衍合后的提交引入到中央服务 器,进一步困扰其他人(译注:这个例子中,出问题的责任方是那个发布了 C6 后又用衍合发布 C4’ 的人,其他人会因此反馈双重历史到共享主干,从而混淆大家的视听。)。

如果把衍合当成一种在推送之前清理提交历史的手段,而且仅仅衍合那些尚未公开的提交对象,就没问题。如果衍合那些已经公开的提交对象,并且已经有人基于这些提交对象开展了后续开发工作的话,就会出现叫人沮丧的麻烦。

3.7  小结
读到这里,你应该已经学会了如何创建分支并切换到新分支,在不同分支间转换,合并本地分支,把分支推送到共享服务器上,使用共享分支与他人协作,以及在分享之前进行衍合。

作者: compare2000    时间: 2014-05-22 16:51
本帖最后由 compare2000 于 2014-05-22 17:03 编辑

Git详解四: 服务器上的Git  
到目前为止,你应该已经学会了使用 Git 来完成日常工作。然而,如果想与他人合作,还需要一个远程的 Git 仓库。尽管技术上可以从个人的仓库里推送和拉取修改内容,但我们不鼓励这样做,因为一不留心就很容易弄混其他人的进度。另外,你也一定希望合作者们即使在 自己不开机的时候也能从仓库获取数据 — 拥有一个更稳定的公共仓库十分有用。因此,更好的合作方式是建立一个大家都可以访问的共享仓库,从那里推送和拉取数据。我们将把这个仓库称为 “Git 服务器”;代理一个 Git 仓库只需要花费很少的资源,几乎从不需要整个服务器来支持它的运行。

架设一台 Git 服务器并不难。第一步是选择与服务器通讯的协议。本章第一节将介绍可用的协议以及各自优缺点。下面一节将介绍一些针对各个协议典型的设置以及如何在服务器 上实施。最后,如果你不介意在他人服务器上保存你的代码,又想免去自己架设和维护服务器的麻烦,倒可以试试我们介绍的几个仓库托管服务。

如果你对架设自己的服务器没兴趣,可以跳到本章最后一节去看看如何申请一个代码托管服务的账户然后继续下一章,我们会在那里讨论分布式源码控制环境的林林总总。

远程仓库通常只是一个_裸仓库(bare repository)_ — 即一个没有当前工作目录的仓库。因为该仓库只是一个合作媒介,所以不需要从硬盘上取出最新版本的快照;仓库里存放的仅仅是 Git 的数据。简单地说,裸仓库就是你工作目录中.git 子目录内的内容。





4.1  协议
Git 可以使用四种主要的协议来传输数据:本地传输,SSH 协议,Git 协议和 HTTP 协议。下面分别介绍一下哪些情形应该使用(或避免使用)这些协议。

值得注意的是,除了 HTTP 协议外,其他所有协议都要求在服务器端安装并运行 Git。

本地协议
最基本的就是_本地协议(Local protocol)_,所谓的远程仓库在该协议中的表示,就是硬盘上的另一个目录。这常见于团队每一个成员都对一个共享的文件系统(例如 NFS)拥有访问权,或者比较少见的多人共用同一台电脑的情况。后面一种情况并不安全,因为所有代码仓库实例都储存在同一台电脑里,增加了灾难性数据损失 的可能性。

如果你使用一个共享的文件系统,就可以在一个本地文件系统中克隆仓库,推送和获取。克隆的时候只需要将远程仓库的路径作为 URL 使用,比如下面这样:

$ git clone /opt/git/project.git或者这样:

$ git clone file:///opt/git/project.git如果在 URL 开头明确使用 file:// ,那么 Git 会以一种略微不同的方式运行。如果你只给出路径,Git 会尝试使用硬链接或直接复制它所需要的文件。如果使用了file:// ,Git 会调用它平时通过网络来传输数据的工序,而这种方式的效率相对较低。使用 file:// 前缀的主要原因是当你需要一个不包含无关引用或对象的干净仓库副本的时候 — 一般指从其他版本控制系统导入的,或类似情形(参见第 9 章的维护任务)。我们这里仅仅使用普通路径,这样更快。

要添加一个本地仓库作为现有 Git 项目的远程仓库,可以这样做:

$ git remote add local_proj /opt/git/project.git然后就可以像在网络上一样向这个远程仓库推送和获取数据了。

优点
基于文件仓库的优点在于它的简单,同时保留了现存文件的权限和网络访问权限。如果你的团队已经有一个全体共享的文件系统,建立仓库就十分容易了。你 只需把一份裸仓库的副本放在大家都能访问的地方,然后像对其他共享目录一样设置读写权限就可以了。我们将在下一节“在服务器上部署 Git ”中讨论如何导出一个裸仓库的副本。

这也是从别人工作目录中获取工作成果的快捷方法。假如你和你的同事在一个项目中合作,他们想让你检出一些东西的时候,运行类似 git pull /home/john/project 通常会比他们推送到服务器,而你再从服务器获取简单得多。

缺点
这种方法的缺点是,与基本的网络连接访问相比,难以控制从不同位置来的访问权限。如果你想从家里的笔记本电脑上推送,就要先挂载远程硬盘,这和基于网络连接的访问相比更加困难和缓慢。

另一个很重要的问题是该方法不一定就是最快的,尤其是对于共享挂载的文件系统。本地仓库只有在你对数据访问速度快的时候才快。在同一个服务器上,如果二者同时允许 Git 访问本地硬盘,通过 NFS 访问仓库通常会比 SSH 慢。

SSH 协议
Git 使用的传输协议中最常见的可能就是 SSH 了。这是因为大多数环境已经支持通过 SSH 对服务器的访问 — 即便还没有,架设起来也很容易。SSH 也是唯一一个同时支持读写操作的网络协议。另外两个网络协议(HTTP 和 Git)通常都是只读的,所以虽然二者对大多数人都可用,但执行写操作时还是需要 SSH。SSH 同时也是一个验证授权的网络协议;而因为其普遍性,一般架设和使用都很容易。

通过 SSH 克隆一个 Git 仓库,你可以像下面这样给出 ssh:// 的 URL:

$ git clone ssh://user@server:project.git或者不指明某个协议 — 这时 Git 会默认使用 SSH :

$ git clone user@server:project.git如果不指明用户,Git 会默认使用当前登录的用户名连接服务器。

优点
使用 SSH 的好处有很多。首先,如果你想拥有对网络仓库的写权限,基本上不可能不使用 SSH。其次,SSH 架设相对比较简单 — SSH 守护进程很常见,很多网络管理员都有一些使用经验,而且很多操作系统都自带了它或者相关的管理工具。再次,通过 SSH 进行访问是安全的 — 所有数据传输都是加密和授权的。最后,和 Git 及本地协议一样,SSH 也很高效,会在传输之前尽可能压缩数据。

缺点
SSH 的限制在于你不能通过它实现仓库的匿   名访问。即使仅为读取数据,人们也必须在能通过 SSH 访问主机的前提下才能访问仓库,这使得 SSH 不利于开源的项目。如果你仅仅在公司网络里使用,SSH 可能是你唯一需要使用的协议。如果想允许对项目的匿名只读访问,那么除了为自己推送而架设 SSH 协议之外,还需要支持其他协议以便他人访问读取。

Git 协议
接下来是 Git 协议。这是一个包含在 Git 软件包中的特殊守护进程; 它会监听一个提供类似于 SSH 服务的特定端口(9418),而无需任何授权。打算支持 Git 协议的仓库,需要先创建git-export-daemon-ok 文件 — 它是协议进程提供仓库服务的必要条件 — 但除此之外该服务没有什么安全措施。要么所有人都能克隆 Git 仓库,要么谁也不能。这也意味着该协议通常不能用来进行推送。你可以允许推送操作;然而由于没有授权机制,一旦允许该操作,网络上任何一个知道项目 URL 的人将都有推送权限。不用说,这是十分罕见的情况。

作者: compare2000    时间: 2014-05-22 16:53
本帖最后由 compare2000 于 2014-05-22 17:04 编辑

优点
Git 协议是现存最快的传输协议。如果你在提供一个有很大访问量的公共项目,或者一个不需要对读操作进行授权的庞大项目,架设一个 Git 守护进程来供应仓库是个不错的选择。它使用与 SSH 协议相同的数据传输机制,但省去了加密和授权的开销。

缺点
Git 协议消 极的一面是缺少授权机制。用 Git 协议作为访问项目的唯一方法通常是不可取的。一般的做法是,同时提供 SSH 接口,让几个开发者拥有推送(写)权限,其他人通过git:// 拥有只读权限。Git 协议可能也是最难架设的协议。它要求有单独的守护进程,需要定制 — 我们将在本章的 “Gitosis” 一节详细介绍它的架设 — 需要设定xinetd 或类似的程序,而这些工作就没那么轻松了。该协议还要求防火墙开放 9418 端口,而企业级防火墙一般不允许对这个非标准端口的访问。大型企业级防火墙通常会封锁这个少见的端口。


作者: compare2000    时间: 2014-05-22 17:07
本帖最后由 compare2000 于 2014-05-22 17:21 编辑

HTTP/S 协议
最后还有 HTTP 协议。HTTP 或 HTTPS 协议的优美之处在于架设的简便性。基本上,只需要把 Git 的裸 仓库文件放在 HTTP 的根目录下,配置一个特定的post-update 挂 钩(hook)就可以搞定(Git 挂钩的细节见第 7 章)。此后,每个能访问 Git 仓库所在服务器上 web 服务的人都可以进行克 隆操作。下面的操作可以允许通过 HTTP 对仓库进行读取:

$ cd /var/www/htdocs/$ git clone --bare /path/to/git_project gitproject.git$ cd gitproject.git$ mv hooks/post-update.sample hooks/post-update$ chmod a+x hooks/post-update这样就可以了。Git 附带的 post-update 挂 钩会默认运行合适的命令(git update-server-info)来确保通过 HTTP 的获取和克 隆正常工作。这条命令在你用 SSH 向仓库推送内容时运行;之后,其他人就可以用下面的命令来克 隆仓库:


$ git clone http://example.com/gitproject.git在本例中,我们使用了 Apache 设定中常用的 /var/www/htdocs 路径,不过你可以使用任何静态 web 服务 — 把裸 仓库放在它的目录里就行。 Git 的数据是以最基本的静态文件的形式提供的(关于如何提供文件的详情见第 9 章)。

通过 HTTP 进行推送操作也是可能的,不过这种做法不太常见,并且牵扯到复杂的 WebDAV 设定。由于很少用到,本书将略过对该内容的讨论。如果对 HTTP 推送协议感兴趣,不妨打开这个地址看一下操作方法:http://www.kernel.org/pub/softwa ... erver-over-http.txt 。通过 HTTP 推送的好处之一是你可以使用任何 WebDAV 服务器,不需要为 Git 设定特殊环境;所以如果主机提供商支持通过 WebDAV 更新网站内容,你也可以使用这项功能。

优点
使用 HTTP 协议的好处是易于架设。几条必要的命令就可以让全世界读取到仓库的内容。花费不过几分钟。HTTP 协议不会占用过多服务器资源。因为它一般只用到静态的 HTTP 服务提供所有数据,普通的 Apache 服务器平均每秒能支撑数千个文件的并发访问 — 哪怕让一个小型服务器超载都很难。

你也可以通过 HTTPS 提供只读的仓库,这意味着你可以加密传输内容;你甚至可以要求客户端使用特定签名的 SSL 证书。一般情况下,如果到了这一步,使用 SSH 公共密钥可能是更简单的方案;不过也存在一些特殊情况,这时通过 HTTPS 使用带签名的 SSL 证书或者其他基于 HTTP 的只读连接授权方式是更好的解决方案。

HTTP 还有个额外的好处:HTTP 是一个如此常见的协议,以至于企业级防火墙通常都允许其端口的通信。

缺点
HTTP 协议的消极面在于,相对来说客户端效率更低。克隆或者下载仓库内容可能会花费更多时间,而且 HTTP 传输的体积和网络开销比其他任何一个协议都大。因为它没有按需供应的能力 — 传输过程中没有服务端的动态计算 — 因而 HTTP 协议经常会被称为_傻 瓜(dumb)_协议。更多 HTTP 协议和其他协议效率上的差异见第 9 。

4.2  在服务器上部署 Git
开始架设 Git 服务器前,需要先把现有仓库导出为裸 仓库 — 即一个不包含当前工作目录的仓库。做法直截了当,克隆时用 --bare 选项即可。裸 仓库的目录名一般以.git 结尾,像这样:

$ git clone --bare my_project my_project.gitInitialized empty Git repository in /opt/projects/my_project.git/该命令的输出或许会让人有些不解。其实 clone 操作基本上相当于 git init 加 git fetch,所以这里出现的其实是git init 的输出,先由它建立一个空目录,而之后传输数据对象的操作并无任何输出,只是悄悄在幕后执行。现在 my_project.git 目录中已经有了一份 Git 目录数据的副本。

整体上的效果大致相当于:

$ cp -Rf my_project/.git my_project.git但在配置文件中有若干小改动,不过对用户来讲,使用方式都一样,不会有什么影响。它仅取出 Git 仓库的必要原始数据,存放在该目录中,而不会另外创建工作目录。

把裸 仓库移到服务器上
有了裸 仓库的副本后,剩下的就是把它放到服务器上并设定相关协议。假设一个域名为 git.example.com 的服务器已经架设好,并可以通过 SSH 访问,我们打算把所有 Git 仓库储存在/opt/git 目录下。只要把裸  仓库复制过去:

$ scp -r my_project.git user@git.example.com:/opt/git现在,所有对该服务器有 SSH 访问权限,并可读取 /opt/git 目录的用户都可以用下面的命令克隆该项目:

$ git clone user@git.example.com:/opt/git/my_project.git如果某个 SSH 用户对 /opt/git/my_project.git 目录有写权限,那他就有推送权限。如果到该项目目录中运行 git init 命令,并加上 --shared 选项,那么 Git 会自动修改该仓库目录的组权限为可写(译注:实际上 --shared 可以指定其他行为,只是默认为将组权限改为可写并执行 g+sx,所以最后会得到 rws。)。

$ ssh user@git.example.com$ cd /opt/git/my_project.git$ git init --bare --shared由此可见,根据现有的 Git 仓库创建一个裸  仓库,然后把它放上你和同事都有 SSH 访问权的服务器是多么容易。现在已经可以开始在同一项目上密切合作了。

值得注意的是,这的的确确是架设一个少数人具有连接权的 Git 服务的全部 — 只要在服务器上加入可以用 SSH 登录的帐号,然后把裸 仓库放在大家都有读写权限的地方。一切都准备停当,无需更多。

下面的几节中,你会了解如何扩展到更复杂的设定。这些内容包含如何避免为每一个用户建立一个账户,给仓库添加公共读取权限,架设网页界面,使用 Gitosis 工具等等。然而,只是和几个人在一个不公开的项目上合作的话,仅仅是一个 SSH 服务器和裸 仓库就足够了,记住这点就可以了。
小型安装
如果设备较少或者你只想在小型开发团队里尝试 Git ,那么一切都很简单。架设 Git 服务最复杂的地方在于账户管理。如果需要仓库对特定的用户可读,而给另一部分用户读写权限,那么访问和许可的安排就比较困难。

SSH 连接
如果已经有了一个所有开发成员都可以用 SSH 访问的服务器,架设第一个服务器将变得异常简单,几乎什么都不用做(正如上节中介绍的那样)。如果需要对仓库进行更复杂的访问控制,只要使用服务器操作系统的本地文件访问许可机制就行了。

如果需要团队里的每个人都对仓库有写权限,又不能给每个人在服务器上建立账户,那么提供 SSH 连接就是唯一的选择了。我们假设用来共享仓库的服务器已经安装了 SSH 服务,而且你通过它访问服务器。

有好几个办法可以让团队的每个人都有访问权。第一个办法是给每个人建立一个账户,直截了当但略过繁琐。反复运行 adduser 并给所有人设定临时密码可不是好玩的。

第二个办法是在主机上建立一个 git 账户,让每个需要写权限的人发送一个 SSH 公钥,然后将其加入 git 账户的~/.ssh/authorized_keys 文件。这样一来,所有人都将通过 git 账户访问主机。这丝毫不会影响提交的数据 — 访问主机用的身份不会影响提交对象的提交者信息。

另一个办法是让 SSH 服务器通过某个 LDAP 服务,或者其他已经设定好的集中授权机制,来进行授权。只要每个人都能获得主机的 shell 访问权,任何可用的 SSH 授权机制都能达到相同效果。




作者: compare2000    时间: 2014-05-22 17:24
本帖最后由 compare2000 于 2014-05-22 17:33 编辑

4.3  生成 SSH 公 钥
大多数 Git 服务器都会选择使用 SSH 公钥来进行授权。系统中的每个用户都必须提供一个公 钥用于授权,没有的话就要生成一个。生成公钥的过程在所有操作系统上都差不多。首先先确认一下是否已经有一个公钥了。SSH 公 钥默认储存在账户的主目录下的~/.ssh 目录。进去看看:

$ cd ~/.ssh$ lsauthorized_keys2  id_dsa       known_hostsconfig            id_dsa.pub关键是看有没有用 something 和 something.pub 来命名的一对文件,这个 something 通常就是 id_dsa 或 id_rsa。有 .pub 后缀的文件就是公 钥,另一个文件则是密 钥。假如没有这些文件,或者干脆连.ssh 目录都没有,可以用 ssh-keygen 来创建。该程序在 Linux/Mac 系统上由 SSH 包提供,而在 Windows 上则包含在 MSysGit 包里:
$ ssh-keygen Generating public/private rsa key pair.Enter file in which to save the key (/Users/schacon/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /Users/schacon/.ssh/id_rsa.Your public key has been saved in /Users/schacon/.ssh/id_rsa.pub.The key fingerprint is:43:c5:5b:5f:b1:f1:50:43:ad:20:a6:92:6a:1f:9a:3a schacon@agadorlaptop.local它先要求你确认保存公 钥的位置(.ssh/id_rsa),然后它会让你重复一个密 码两次,如果不想在使用公钥的时候输入密 码,可以留空。

现在,所有做过这一步的用户都得把它们的公 钥给你或者 Git 服务器的管理员(假设 SSH 服务被设定为使用公 钥机制)。他们只需要复制 .pub 文件的内容然后发邮件给管理员。公 钥的样子大致如下:

$ cat ~/.ssh/id_rsa.pub ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSUGPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XAt3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert/EnmZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbxNrRFi9wrf+M7Q== schacon@agadorlaptop.local关于在多个操作系统上设立相同 SSH 公钥的教程,可以查阅 GitHub 上有关 SSH 公 钥的向导:http://github.com/guides/providing-your-ssh-key

4.4  架设服务器
现在我们过一边服务器端架设 SSH 访问的流程。本例将使用 authorized_keys 方法来给用户授权。我们还将假定使用类似 Ubuntu 这样的标准 Linux 发行版。首先,创建一个名为 ‘git’ 的用户,并为其创建一个.ssh 目录。

$ sudo adduser git$ su git$ cd$ mkdir .ssh接下来,把开发者的 SSH 公钥添加到这个用户的 authorized_keys 文件中。假设你通过电邮收到了几个公钥并存到了临时文件里。重复一下,公钥大致看起来是这个样子:

$ cat /tmp/id_rsa.john.pubssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCB007n/ww+ouN4gSLKssMxXnBOvf9LGt4LojG6rs6hPB09j9R/T17/x4lhJA0F3FR1rP6kYBRsWj2aThGw6HXLm9/5zytK6Ztg3RPKK+4kYjh6541NYsnEAZuXz0jTTyAUfrtU3Z5E003C4oxOj6H0rfIF1kKI9MAQLMdpGW1GYEIgS9EzSdfd8AcCIicTDWbqLAcU4UpkaX8KyGlLwsNuuGztobF8m72ALC/nLF6JLtPofwFBlgc+myivO7TCUSBdLQlgMVOFq1I2uPWQOkOWQAHukEOmfjy2jctxSDBQ220ymjaNsHT4kgtZg2AYYgPqdAv8JggJICUvax2T9va5 gsg-keypair只要把它们逐个追加到 authorized_keys 文件尾部即可:

$ cat /tmp/id_rsa.john.pub >> ~/.ssh/authorized_keys$ cat /tmp/id_rsa.josie.pub >> ~/.ssh/authorized_keys$ cat /tmp/id_rsa.jessica.pub >> ~/.ssh/authorized_keys现在可以用 --bare 选项运行 git init 来建立一个裸 仓库,这会初始化一个不包含工作目录的仓库。

$ cd /opt/git$ mkdir project.git$ cd project.git$ git --bare init这时,Join,Josie 或者 Jessica 就可以把它加为远程仓库,推送一个分支,从而把第一个版本的项目文件上传到仓库里了。值得注意的是,每次添加一个新项目都需要通过 shell 登入主机并创建一个裸 仓库目录。我们不妨以gitserver 作为 git 用户及项目仓库所在的主机名。如果在网络内部运行该主机,并在 DNS 中设定 gitserver 指向该主机,那么以下这些命令都是可用的:

# 在 John 的电脑上$ cd myproject$ git init$ git add .$ git commit -m 'initial commit'$ git remote add origin git@gitserver:/opt/git/project.git$ git push origin master这样,其他人的克 隆和推送也一样变得很简单:
$ git clone git@gitserver:/opt/git/project.git$ vim README$ git commit -am 'fix for the README file'$ git push origin master用这个方法可以很快捷地为少数几个开发者架设一个可读写的 Git 服务。

作为一个额外的防范措施,你可以用 Git 自带的 git-shell 工具限制 git 用户的活动范围。只要把它设为git 用户登入的 shell,那么该用户就无法使用普通的 bash 或者 csh 什么的 shell 程序。编辑 /etc/passwd 文件:

$ sudo vim /etc/passwd在文件末尾,你应该能找到类似这样的行:

git:1000:1000::/home/git:/bin/sh把 bin/sh 改为 /usr/bin/git-shell (或者用 which git-shell 查看它的实际安装路径)。该行修改后的样子如下:

git:1000:1000::/home/git:/usr/bin/git-shell现在 git 用户只能用 SSH 连接来推送和获取 Git 仓库,而不能直接使用主机 shell。尝试普通 SSH 登录的话,会看到下面这样的拒绝信息:

$ ssh git@gitserverfatal: What do you think I am? A shell?Connection to gitserver closed.
4.5  公共访问
匿 名的读取权限该怎么实现呢?也许除了内部私有的项目之外,你还需要托 管一些开 源项目。或者因为要用一些自动化的服务器来进行编译,或者有一些经常变化的服务器群组,而又不想整天生成新的 SSH 密 钥 — 总之,你需要简单的匿 名读取权限。

或许对小型的配置来说最简单的办法就是运行一个静态 web 服务,把它的根目录设定为 Git 仓库所在的位置,然后开启本章第一节提到的 post-update 挂 钩。这里继续使用之前的例子。假设仓库处于/opt/git 目录,主机上运行着 Apache 服务。重申一下,任何 web 服务程序都可以达到相同效果;作为范例,我们将用一些基本的 Apache 设定来展示大体需要的步骤。

首先,开启挂 钩:

$ cd project.git$ mv hooks/post-update.sample hooks/post-update$ chmod a+x hooks/post-update如果用的是 Git 1.6 之前的版本,则可以省略 mv 命令 — Git 是从较晚的版本才开始在挂钩实例的结尾添加 .sample 后缀名的。

post-update 挂 钩是做什么的呢?其内容大致如下:

$ cat .git/hooks/post-update #!/bin/shexec git-update-server-info意思是当通过 SSH 向服务器推送时,Git 将运行这个 git-update-server-info 命令来更新匿 名 HTTP 访问获取数据时所需要的文件。

接下来,在 Apache 配置文件中添加一个 VirtualHost 条目,把文档根目录设为 Git 项目所在的根目录。这里我们假定 DNS 服务已经配置好,会把对.gitserver 的请求发送到这台主机:

ServerName git.gitserver DocumentRoot /opt/git Order allow, deny allow from all  另外,需要把 /opt/git 目录的 Unix 用户组设定为 www-data ,这样 web 服务才可以读取仓库内容,因为运行 CGI 脚本的 Apache 实例进程默认就是以该用户的身份起来的:

$ chgrp -R www-data /opt/git重启 Apache 之后,就可以通过项目的 URL 来克 隆该目录下的仓库了。

$ git clone http://git.gitserver/project.git这一招可以让你在几分钟内为相当数量的用户架设好基于 HTTP 的读取权限。另一个提供非授权访问的简单方法是开启一个 Git 守护进程,不过这将要求该进程作为后台进程常驻 — 接下来的这一节就要讨论这方面的细节。


作者: compare2000    时间: 2014-05-22 17:35
4.6  GitWeb
现在我们的项目已经有了可读可写和只读的连接方式,不过如果能有一个简单的 web 界面访问就更好了。Git 自带一个叫做 GitWeb 的 CGI 脚本,运行效果可以到http://git.kernel.org 这样的站点体验下(见图 4-1)。



Figure 4-1. 基于网页的 GitWeb 用户界面
如果想看看自己项目的效果,不妨用 Git 自带的一个命令,可以使用类似 lighttpd 或 webrick 这样轻量级的服务器启动一个临时进程。如果是在 Linux 主机上,通常都预装了lighttpd ,可以到项目目录中键入 git instaweb 来启动。如果用的是 Mac ,Leopard 预装了 Ruby,所以webrick 应该是最好的选择。如果要用 lighttpd 以外的程序来启动 git instaweb,可以通过--httpd 选项指定:

$ git instaweb --httpd=webrick[2009-02-21 10:02:21] INFO  WEBrick 1.3.1[2009-02-21 10:02:21] INFO  ruby 1.8.6 (2008-03-03) [universal-darwin9.0]这会在 1234 端口开启一个 HTTPD 服务,随之在浏览器中显示该页,十分简单。关闭服务时,只需在原来的命令后面加上 --stop 选项就可以了:

$ git instaweb --httpd=webrick --stop如果需要为团队或者某个开源项目长期运行 GitWeb,那么 CGI 脚本就要由正常的网页服务来运行。一些 Linux 发行版可以通过 apt 或yum 安装一个叫做 gitweb 的软件包,不妨首先尝试一下。我们将快速介绍一下手动安装 GitWeb 的流程。首先,你需要 Git 的源码,其中带有 GitWeb,并能生成定制的 CGI 脚本:

$ git clone git://git.kernel.org/pub/scm/git/git.git$ cd git/$ make GITWEB_PROJECTROOT="/opt/git" \        prefix=/usr gitweb/gitweb.cgi$ sudo cp -Rf gitweb /var/www/注意,通过指定 GITWEB_PROJECTROOT 变量告诉编译命令 Git 仓库的位置。然后,设置 Apache 以 CGI 方式运行该脚本,添加一个 VirtualHost 配置:

ServerName gitserver DocumentRoot /var/www/gitweb Options ExecCGI +FollowSymLinks +SymLinksIfOwnerMatch AllowOverride All order allow,deny Allow from all AddHandler cgi-script cgi DirectoryIndex gitweb.cgi  不难想象,GitWeb 可以使用任何兼容 CGI 的网页服务来运行;如果偏向使用其他 web 服务器,配置也不会很麻烦。现在,通过 http://gitserver 就可以在线访问仓库了,在http://git.server 上还可以通过 HTTP 克隆和获取仓库的内容。





4.7  Gitosis
把所有用户的公钥保存在 authorized_keys 文件的做法,只能凑和一阵子,当用户数量达到几百人的规模时,管理起来就会十分痛苦。每次改删用户都必须登录服务器不去说,这种做法还缺少必要的权限管理 — 每个人都对所有项目拥有完整的读写权限。

幸好我们还可以选择应用广泛的 Gitosis 项目。简单地说,Gitosis 就是一套用来管理 authorized_keys 文件和实现简单连接限制的脚本。有趣的是,用来添加用户和设定权限的并非通过网页程序,而只是管理一个特殊的 Git 仓库。你只需要在这个特殊仓库内做好相应的设定,然后推送到服务器上,Gitosis 就会随之改变运行策略,听起来就很酷,对吧?

Gitosis 的安装算不上傻瓜化,但也不算太难。用 Linux 服务器架设起来最简单 — 以下例子中,我们使用装有 Ubuntu 8.10 系统的服务器。

Gitosis 的工作依赖于某些 Python 工具,所以首先要安装 Python 的 setuptools 包,在 Ubuntu 上称为 python-setuptools:

$ apt-get install python-setuptools接下来,从 Gitosis 项目主页克隆并安装:

$ git clone git://eagain.net/gitosis.git$ cd gitosis$ sudo python setup.py install这会安装几个供 Gitosis 使用的工具。默认 Gitosis 会把 /home/git 作为存储所有 Git 仓库的根目录,这没什么不好,不过我们之前已经把项目仓库都放在/opt/git 里面了,所以为方便起见,我们可以做一个符号连接,直接划转过去,而不必重新配置:

$ ln -s /opt/git /home/git/repositoriesGitosis 将会帮我们管理用户公钥,所以先把当前控制文件改名备份,以便稍后重新添加,准备好让 Gitosis 自动管理 authorized_keys 文件:

$ mv /home/git/.ssh/authorized_keys /home/git/.ssh/ak.bak接下来,如果之前把 git 用户的登录 shell 改为 git-shell 命令的话,先恢复 ‘git’ 用户的登录 shell。改过之后,大家仍然无法通过该帐号登录(译注:因为authorized_keys 文件已经没有了。),不过不用担心,这会交给 Gitosis 来实现。所以现在先打开 /etc/passwd 文件,把这行:

git:1000:1000::/home/git:/usr/bin/git-shell改回:

git:1000:1000::/home/git:/bin/sh好了,现在可以初始化 Gitosis 了。你可以用自己的公钥执行 gitosis-init 命令,要是公钥不在服务器上,先临时复制一份:

$ sudo -H -u git gitosis-init < /tmp/id_dsa.pubInitialized empty Git repository in /opt/git/gitosis-admin.git/Reinitialized existing Git repository in /opt/git/gitosis-admin.git/这样该公钥的拥有者就能修改用于配置 Gitosis 的那个特殊 Git 仓库了。接下来,需要手工对该仓库中的 post-update 脚本加上可执行权限:

$ sudo chmod 755 /opt/git/gitosis-admin.git/hooks/post-update基本上就算是好了。如果设定过程没出什么差错,现在可以试一下用初始化 Gitosis 的公钥的拥有者身份 SSH 登录服务器,应该会看到类似下面这样:

$ ssh git@gitserverPTY allocation request failed on channel 0fatal: unrecognized command 'gitosis-serve schacon@quaternion'  Connection to gitserver closed.说明 Gitosis 认出了该用户的身份,但由于没有运行任何 Git 命令,所以它切断了连接。那么,现在运行一个实际的 Git 命令 — 克隆 Gitosis 的控制仓库:

# 在你本地计算机上$ git clone git@gitserver:gitosis-admin.git这会得到一个名为 gitosis-admin 的工作目录,主要由两部分组成:

$ cd gitosis-admin$ find ../gitosis.conf./keydir./keydir/scott.pubgitosis.conf 文件是用来设置用户、仓库和权限的控制文件。keydir 目录则是保存所有具有访问权限用户公钥的地方— 每人一个。在keydir 里的文件名(比如上面的 scott.pub)应该跟你的不一样 — Gitosis 会自动从使用 gitosis-init 脚本导入的公钥尾部的描述中获取该名字。

看一下 gitosis.conf 文件的内容,它应该只包含与刚刚克隆的 gitosis-admin 相关的信息:

$ cat gitosis.conf [gitosis][group gitosis-admin]writable = gitosis-adminmembers = scott它显示用户 scott — 初始化 Gitosis 公钥的拥有者 — 是唯一能管理 gitosis-admin 项目的人。

现在我们来添加一个新项目。为此我们要建立一个名为 mobile 的新段落,在其中罗列手机开发团队的开发者,以及他们拥有写权限的项目。由于 ‘scott’ 是系统中的唯一用户,我们把他设为唯一用户,并允许他读写名为iphone_project 的新项目:

[group mobile]writable = iphone_projectmembers = scott修改完之后,提交 gitosis-admin 里的改动,并推送到服务器使其生效:

$ git commit -am 'add iphone_project and mobile group'[master]: created 8962da8: "changed name" 1 files changed, 4 insertions(+), 0 deletions(-)$ git pushCounting objects: 5, done.Compressing objects: 100% (2/2), done.Writing objects: 100% (3/3), 272 bytes, done.Total 3 (delta 1), reused 0 (delta 0)To git@gitserver:/opt/git/gitosis-admin.git   fb27aec..8962da8  master -> master在新工程 iphone_project 里首次推送数据到服务器前,得先设定该服务器地址为远程仓库。但你不用事先到服务器上手工创建该项目的裸仓库— Gitosis 会在第一次遇到推送时自动创建:

$ git remote add origin git@gitserver:iphone_project.git$ git push origin masterInitialized empty Git repository in /opt/git/iphone_project.git/Counting objects: 3, done.Writing objects: 100% (3/3), 230 bytes, done.Total 3 (delta 0), reused 0 (delta 0)To git@gitserver:iphone_project.git * [new branch]      master -> master请注意,这里不用指明完整路径(实际上,如果加上反而没用),只需要一个冒号加项目名字即可 — Gitosis 会自动帮你映射到实际位置。

要和朋友们在一个项目上协同工作,就得重新添加他们的公钥。不过这次不用在服务器上一个一个手工添加到 ~/.ssh/authorized_keys 文件末端,而只需管理keydir 目录中的公钥文件。文件的命名将决定在 gitosis.conf 中对用户的标识。现在我们为 John,Josie 和 Jessica 添加公钥:

$ cp /tmp/id_rsa.john.pub keydir/john.pub$ cp /tmp/id_rsa.josie.pub keydir/josie.pub$ cp /tmp/id_rsa.jessica.pub keydir/jessica.pub然后把他们都加进 ‘mobile’ 团队,让他们对 iphone_project 具有读写权限:

[group mobile]writable = iphone_projectmembers = scott john josie jessica如果你提交并推送这个修改,四个用户将同时具有该项目的读写权限。

Gitosis 也具有简单的访问控制功能。如果想让 John 只有读权限,可以这样做:

[group mobile]writable = iphone_projectmembers = scott josie jessica[group mobile_ro]readonly = iphone_projectmembers = john现在 John 可以克隆和获取更新,但 Gitosis 不会允许他向项目推送任何内容。像这样的组可以随意创建,多少不限,每个都可以包含若干不同的用户和项目。甚至还可以指定某个组为成员之一(在组名前加上@ 前缀),自动继承该组的成员:

[group mobile_committers]members = scott josie jessica[group mobile]writable  = iphone_projectmembers   = @mobile_committers[group mobile_2]writable  = another_iphone_projectmembers   = @mobile_committers john如果遇到意外问题,试试看把 loglevel=DEBUG 加到 [gitosis] 的段落(译注:把日志设置为调试级别,记录更详细的运行信息。)。如果一不小心搞错了配置,失去了推送权限,也可以手工修改服务器上的/home/git/.gitosis.conf 文件 — Gitosis 实际是从该文件读取信息的。它在得到推送数据时,会把新的 gitosis.conf 存到该路径上。所以如果你手工编辑该文件的话,它会一直保持到下次向 gitosis-admin 推送新版本的配置内容为止。

作者: compare2000    时间: 2014-05-22 17:35
4.8  Gitolite
Note: the latest copy of this section of the ProGit book is always available within thegitolite documentation. The author would also like to humbly state that, while this section is accurate, andcan (and often has) been used to install gitolite without reading any other documentation, it is of necessity not complete, and cannot completely replace the enormous amount of documentation that gitolite comes with.

Git has started to become very popular in corporate environments, which tend to have some additional requirements in terms of access control. Gitolite was originally created to help with those requirements, but it turns out that it’s equally useful in the open source world: the Fedora Project controls access to their package management repositories (over 10,000 of them!) using gitolite, and this is probably the largest gitolite installation anywhere too.

Gitolite allows you to specify permissions not just by repository, but also by branch or tag names within each repository. That is, you can specify that certain people (or groups of people) can only push certain “refs” (branches or tags) but not others.

Installing
Installing Gitolite is very easy, even if you don’t read the extensive documentation that comes with it. You need an account on a Unix server of some kind; various Linux flavours, and Solaris 10, have been tested. You do not need root access, assuming git, perl, and an openssh compatible ssh server are already installed. In the examples below, we will use thegitolite account on a host called gitserver.

Gitolite is somewhat unusual as far as “server” software goes – access is via ssh, and so every userid on the server is a potential “gitolite host”. As a result, there is a notion of “installing” the software itself, and then “setting up” a user as a “gitolite host”.

Gitolite has 4 methods of installation. People using Fedora or Debian systems can obtain an RPM or a DEB and install that. People with root access can install it manually. In these two methods, any user on the system can then become a “gitolite host”.

People without root access can install it within their own userids. And finally, gitolite can be installed by running a scripton the workstation, from a bash shell. (Even the bash that comes with msysgit will do, in case you’re wondering.)

We will describe this last method in this article; for the other methods please see the documentation.

You start by obtaining public key based access to your server, so that you can log in from your workstation to the server without getting a password prompt. The following method works on Linux; for other workstation OSs you may have to do this manually. We assume you already had a key pair generated using ssh-keygen.

$ ssh-copy-id -i ~/.ssh/id_rsa gitolite@gitserverThis will ask you for the password to the gitolite account, and then set up public key access. This isessential for the install script, so check to make sure you can run a command without getting a password prompt:

$ ssh gitolite@gitserver pwd/home/gitoliteNext, you clone Gitolite from the project’s main site and run the “easy install” script (the third argument is your name as you would like it to appear in the resulting gitolite-admin repository):

$ git clone git://github.com/sitaramc/gitolite$ cd gitolite/src$ ./gl-easy-install -q gitolite gitserver sitaramAnd you’re done! Gitolite has now been installed on the server, and you now have a brand new repository calledgitolite-admin in the home directory of your workstation. You administer your gitolite setup by making changes to this repository and pushing.

That last command does produce a fair amount of output, which might be interesting to read. Also, the first time you run this, a new keypair is created; you will have to choose a passphrase or hit enter for none. Why a second keypair is needed, and how it is used, is explained in the “ssh troubleshooting” document that comes with Gitolite. (Hey the documentation has to be good forsomething!)

Repos named gitolite-admin and testing are created on the server by default. If you wish to clone either of these locally (from an account that has SSH console access to the gitolite account viaauthorized_keys), type:

$ git clone gitolite:gitolite-admin$ git clone gitolite:testingTo clone these same repos from any other account:

$ git clone gitolite@servername:gitolite-admin$ git clone gitolite@servername:testingCustomising the Install
While the default, quick, install works for most people, there are some ways to customise the install if you need to. If you omit the-q argument, you get a “verbose” mode install – detailed information on what the install is doing at each step. The verbose mode also allows you to change certain server-side parameters, such as the location of the actual repositories, by editing an “rc” file that the server uses. This “rc” file is liberally commented so you should be able to make any changes you need quite easily, save it, and continue. This file also contains various settings that you can change to enable or disable some of gitolite’s advanced features.

Config File and Access Control Rules
Once the install is done, you switch to the gitolite-admin repository (placed in your HOME directory) and poke around to see what you got:

$ cd ~/gitolite-admin/$ lsconf/  keydir/$ find conf keydir -type fconf/gitolite.confkeydir/sitaram.pub$ cat conf/gitolite.conf#gitolite conf# please see conf/example.conf for details on syntax and featuresrepo gitolite-admin    RW+                 = sitaramrepo testing    RW+                 = @allNotice that “sitaram” (the last argument in the gl-easy-install command you gave earlier) has read-write permissions on thegitolite-admin repository as well as a public key file of the same name.

The config file syntax for gitolite is liberally documented in conf/example.conf, so we’ll only mention some highlights here.

You can group users or repos for convenience. The group names are just like macros; when defining them, it doesn’t even matter whether they are projects or users; that distinction is only made when youuse the “macro”.

@oss_repos      = linux perl rakudo git gitolite@secret_repos   = fenestra pear@admins         = scott     # Adams, not Chacon, sorry @interns        = ashok     # get the spelling right, Scott!@engineers      = sitaram dilbert wally alice@staff          = @admins @engineers @internsYou can control permissions at the “ref” level. In the following example, interns can only push the “int” branch. Engineers can push any branch whose name starts with “eng-“, and tags that start with “rc” followed by a digit. And the admins can do anything (including rewind) to any ref.

repo @oss_repos    RW  int$                = @interns    RW  eng-                = @engineers    RW  refs/tags/rc[0-9]   = @engineers    RW+                     = @adminsThe expression after the RW or RW+ is a regular expression (regex) that the refname (ref) being pushed is matched against. So we call it a “refex”! Of course, a refex can be far more powerful than shown here, so don’t overdo it if you’re not comfortable with perl regexes.

Also, as you probably guessed, Gitolite prefixes refs/heads/ as a syntactic convenience if the refex does not begin withrefs/.

An important feature of the config file’s syntax is that all the rules for a repository need not be in one place. You can keep all the common stuff together, like the rules for alloss_repos shown above, then add specific rules for specific cases later on, like so:

repo gitolite    RW+                     = sitaramThat rule will just get added to the ruleset for the gitolite repository.

At this point you might be wondering how the access control rules are actually applied, so let’s go over that briefly.

There are two levels of access control in gitolite. The first is at the repository level; if you have read (or write) access toany ref in the repository, then you have read (or write) access to the repository.

The second level, applicable only to “write” access, is by branch or tag within a repository. The username, the access being attempted (W or+), and the refname being updated are known. The access rules are checked in order of appearance in the config file, looking for a match for this combination (but remember that the refname is regex-matched, not merely string-matched). If a match is found, the push succeeds. A fallthrough results in access being denied.

Advanced Access Control with “deny” rules
So far, we’ve only seen permissions to be one or R, RW, orRW+. However, gitolite allows another permission: -, standing for “deny”. This gives you a lot more power, at the expense of some complexity, because now fallthrough is not theonly way for access to be denied, so the order of the rules now matters!

Let us say, in the situation above, we want engineers to be able to rewind any branchexcept master and integ. Here’s how to do that:

    RW  master integ    = @engineers    -   master integ    = @engineers    RW+                 = @engineersAgain, you simply follow the rules top down until you hit a match for your access mode, or a deny. Non-rewind push to master or integ is allowed by the first rule. A rewind push to those refs does not match the first rule, drops down to the second, and is therefore denied. Any push (rewind or non-rewind) to refs other than master or integ won’t match the first two rules anyway, and the third rule allows it.

Restricting pushes by files changed
In addition to restricting what branches a user can push changes to, you can also restrict what files they are allowed to touch. For example, perhaps the Makefile (or some other program) is really not supposed to be changed by just anyone, because a lot of things depend on it or would break if the changes are not done just right. You can tell gitolite:

repo foo    RW                  =   @junior_devs @senior_devs    RW  NAME/           =   @senior_devs    -   NAME/Makefile   =   @junior_devs    RW  NAME/           =   @junior_devsThis powerful feature is documented in conf/example.conf.

Personal Branches
Gitolite also has a feature called “personal branches” (or rather, “personal branch namespace”) that can be very useful in a corporate environment.

A lot of code exchange in the git world happens by “please pull” requests. In a corporate environment, however, unauthenticated access is a no-no, and a developer workstation cannot do authentication, so you have to push to the central server and ask someone to pull from there.

This would normally cause the same branch name clutter as in a centralised VCS, plus setting up permissions for this becomes a chore for the admin.

Gitolite lets you define a “personal” or “scratch” namespace prefix for each developer (for example,refs/personal/ /* ); see the “personal branches” section in doc/3-faq-tips-etc.mkd for details.

“Wildcard” repositories
Gitolite allows you to specify repositories with wildcards (actually perl regexes), like, for exampleassignments/s[0-9][0-9]/a[0-9][0-9], to pick a random example. This is avery powerful feature, which has to be enabled by setting $GL_WILDREPOS = 1; in the rc file. It allows you to assign a new permission mode (”C”) which allows users to create repositories based on such wild cards, automatically assigns ownership to the specific user who created it, allows him/her to hand out R and RW permissions to other users to collaborate, etc. This feature is documented indoc/4-wildcard-repositories.mkd.

Other Features
We’ll round off this discussion with a sampling of other features, all of which, and many more, are described in great detail in the “faqs, tips, etc” and other documents.

Logging: Gitolite logs all successful accesses. If you were somewhat relaxed about giving people rewind permissions (RW+) and some kid blew away “master”, the log file is a life saver, in terms of easily and quickly finding the SHA that got hosed.

Git outside normal PATH: One extremely useful convenience feature in gitolite is support for git installed outside the normal$PATH (this is more common than you think; some corporate environments or even some hosting providers refuse to install things system-wide and you end up putting them in your own directories). Normally, you are forced to make theclient-side git aware of this non-standard location of the git binaries in some way. With gitolite, just choose a verbose install and set$GIT_PATH in the “rc” files. No client-side changes are required after that

Access rights reporting: Another convenient feature is what happens when you try and just ssh to the server. Gitolite shows you what repos you have access to, and what that access may be. Here’s an example:

    hello sitaram, the gitolite version here is v1.5.4-19-ga3397d4    the gitolite config gives you the following access:         R     anu-wsd         R     entrans         R  W  git-notes         R  W  gitolite         R  W  gitolite-admin         R     indic_web_input         R     shreelipi_converterDelegation: For really large installations, you can delegate responsibility for groups of repositories to various people and have them manage those pieces independently. This reduces the load on the main admin, and makes him less of a bottleneck. This feature has its own documentation file in the doc/ directory.

Gitweb support: Gitolite supports gitweb in several ways. You can specify which repos are visible via gitweb. You can set the “owner” and “description” for gitweb from the gitolite config file. Gitweb has a mechanism for you to implement access control based on HTTP authentication, so you can make it use the “compiled” config file that gitolite produces, which means the same access control rules (for read access) apply for gitweb and gitolite.

Mirroring: Gitolite can help you maintain multiple mirrors, and switch between them easily if the primary server goes down.



4.9  Git 守护进程
对于提供公共的,非授权的只读访问,我们可以抛弃 HTTP 协议,改用 Git 自己的协议,这主要是出于性能和速度的考虑。Git 协议远比 HTTP 协议高效,因而访问速度也快,所以它能节省很多用户的时间。

重申一下,这一点只适用于非授权的只读访问。如果建在防火墙之外的服务器上,那么它所提供的服务应该只是那些公开的只读项目。如果是在防火墙之内的 服务器上,可用于支撑大量参与人员或自动系统(用于持续集成或编译的主机)只读访问的项目,这样可以省去逐一配置 SSH 公钥的麻烦。

但不管哪种情形,Git 协议的配置设定都很简单。基本上,只要以守护进程的形式运行该命令即可:

git daemon --reuseaddr --base-path=/opt/git/ /opt/git/这里的 --reuseaddr 选项表示在重启服务前,不等之前的连接超时就立即重启。而 --base-path 选项则允许克隆项目时不必给出完整路径。最后面的路径告诉 Git 守护进程允许开放给用户访问的仓库目录。假如有防火墙,则需要为该主机的 9418 端口设置为允许通信。

以守护进程的形式运行该进程的方法有很多,但主要还得看用的是什么操作系统。在 Ubuntu 主机上,可以用 Upstart 脚本达成。编辑该文件:

/etc/event.d/local-git-daemon加入以下内容:

start on startupstop on shutdownexec /usr/bin/git daemon \    --user=git --group=git \    --reuseaddr \    --base-path=/opt/git/ \    /opt/git/respawn出于安全考虑,强烈建议用一个对仓库只有读取权限的用户身份来运行该进程 — 只需要简单地新建一个名为 git-ro 的用户(译注:新建用户默认对仓库文件不具备写权限,但这取决于仓库目录的权限设定。务必确认git-ro 对仓库只能读不能写。),并用它的身份来启动进程。这里为了简化,后面我们还是用之前运行 Gitosis 的用户 ‘git’。

这样一来,当你重启计算机时,Git 进程也会自动启动。要是进程意外退出或者被杀掉,也会自行重启。在设置完成后,不重启计算机就启动该守护进程,可以运行:

initctl start local-git-daemon而在其他操作系统上,可以用 xinetd,或者 sysvinit 系统的脚本,或者其他类似的脚本 — 只要能让那个命令变为守护进程并可监控。

接下来,我们必须告诉 Gitosis 哪些仓库允许通过 Git 协议进行匿名只读访问。如果每个仓库都设有各自的段落,可以分别指定是否允许 Git 进程开放给用户匿名读取。比如允许通过 Git 协议访问 iphone_project,可以把下面两行加到gitosis.conf 文件的末尾:

[repo iphone_project]daemon = yes在提交和推送完成后,运行中的 Git 守护进程就会响应来自 9418 端口对该项目的访问请求。

如果不考虑 Gitosis,单单起了 Git 守护进程的话,就必须到每一个允许匿名只读访问的仓库目录内,创建一个特殊名称的空文件作为标志:

$ cd /path/to/project.git$ touch git-daemon-export-ok该文件的存在,表明允许 Git 守护进程开放对该项目的匿名只读访问。

Gitosis 还能设定哪些项目允许放在 GitWeb 上显示。先打开 GitWeb 的配置文件 /etc/gitweb.conf,添加以下四行:

$projects_list = "/home/git/gitosis/projects.list";$projectroot = "/home/git/repositories";$export_ok = "git-daemon-export-ok";@git_base_url_list = ('git://gitserver');接下来,只要配置各个项目在 Gitosis 中的 gitweb 参数,便能达成是否允许 GitWeb 用户浏览该项目。比如,要让 iphone_project 项目在 GitWeb 里出现,把repo 的设定改成下面的样子:

[repo iphone_project]daemon = yesgitweb = yes在提交并推送过之后,GitWeb 就会自动开始显示 iphone_project 项目的细节和历史。





4.10  Git 托管服务
如果不想经历自己架设 Git 服务器的麻烦,网络上有几个专业的仓库托管服务可供选择。这样做有几大优点:托管账户的建立通常比较省时,方便项目的启动,而且不涉及服务器的维护和监 控。即使内部创建并运行着自己的服务器,同时为开源项目提供一个公共托管站点还是有好处的 — 让开源社区更方便地找到该项目,并给予帮助。

目前,可供选择的托管服务数量繁多,各有利弊。在 Git 官方 wiki 上的 Githosting 页面有一个最新的托管服务列表:

http://git.or.cz/gitwiki/GitHosting由于本书无法全部一一介绍,而本人(译注:指本书作者 Scott Chacon。)刚好在其中一家公司工作,所以接下来我们将会介绍如何在 GitHub 上建立新账户并启动项目。至于其他托管服务大体也是这么一个过程,基本的想法都是差不多的。

GitHub 是目前为止最大的开源 Git 托管服务,并且还是少数同时提供公共代码和私有代码托管服务的站点之一,所以你可以在上面同时保存开源和商业代码。事实上,本书就是放在 GitHub 上合作编著的。(译注:本书的翻译也是放在 GitHub 上广泛协作的。)

GitHub
GitHub 和大多数的代码托管站点在处理项目命名空间的方式上略有不同。GitHub 的设计更侧重于用户,而不是完全基于项目。也就是说,如果我在 GitHub 上托管一个名为grit 的项目的话,它的地址不会是 github.com/grit,而是按在用户底下 github.com/shacon/grit (译注:本书作者 Scott Chacon 在 GitHub 上的用户名是shacon。)。不存在所谓某个项目的官方版本,所以假如第一作者放弃了某个项目,它可以无缝转移到其它用户的名下。

GitHub 同时也是一个向使用私有仓库的用户收取费用的商业公司,但任何人都可以方便快捷地申请到一个免费账户,并在上面托管数量不限的开源项目。接下来我们快速介绍一下 GitHub 的基本使用。

建立新账户
首先注册一个免费账户。访问 Pricing and Signup 页面 http://github.com/plans 并点击 Free acount 里的 Sign Up 按钮(见图 4-2),进入注册页面。



图 4-2. GitHub 服务简介页面
选择一个系统中尚未使用的用户名,提供一个与之相关联的电邮地址,并输入密码(见图 4-3):



图 4-3. GitHub 用户注册表单
如果方便,现在就可以提供你的 SSH 公钥。我们在前文的”小型安装” 一节介绍过生成新公钥的方法。把新生成的公钥复制粘贴到 SSH Public Key 文本框中即可。要是对生成公钥的步骤不太清楚,也可以点击 “explain ssh keys” 链接,会显示各个主流操作系统上完成该步骤的介绍。点击 “I agree,sign me up” 按钮完成用户注册,并转到该用户的 dashboard 页面(见图 4-4):



图 4-4. GitHub 的用户面板
接下来就可以建立新仓库了。

建立新仓库
点击用户面板上仓库旁边的 “create a new one” 链接,显示 Create a New Repository 的表单(见图 4-5):



图 4-5. 在 GitHub 上建立新仓库
当然,项目名称是必不可少的,此外也可以适当描述一下项目的情况或者给出官方站点的地址。然后点击 “Create Repository” 按钮,新仓库就建立起来了(见图 4-6):



图 4-6. GitHub 上各个项目的概要信息
由于尚未提交代码,点击项目主页后 GitHub 会显示一个简要的指南,告诉你如何新建一个项目并推送上来,如何从现有项目推送,以及如何从一个公共的 Subversion 仓库导入项目(见图 4-7):



图 4-7. 新仓库指南
该指南和本书前文介绍的类似,对于新的项目,需要先在本地初始化为 Git 项目,添加要管理的文件并作首次提交:

$ git init$ git add .$ git commit -m 'initial commit'然后在这个本地仓库内把 GitHub 添加为远程仓库,并推送 master 分支上来:

$ git remote add origin git@github.com:testinguser/iphone_project.git$ git push origin master现在该项目就托管在 GitHub 上了。你可以把它的 URL 分享给每位对此项目感兴趣的人。本例的 URL 是 http://github.com/testinguser/iphone_project。而在项目页面的摘要部分,你会发现有两个 Git URL 地址(见图 4-8):



图 4-8. 项目摘要中的公共 URL 和私有 URL
Public Clone URL 是一个公开的,只读的 Git URL,任何人都可以通过它克隆该项目。可以随意散播这个 URL,比如发布到个人网站之类的地方等等。

Your Clone URL 是一个基于 SSH 协议的可读可写 URL,只有使用与上传的 SSH 公钥对应的密钥来连接时,才能通过它进行读写操作。其他用户访问该项目页面时只能看到之前那个公共的 URL,看不到这个私有的 URL。

从 Subversion 导入项目
如果想把某个公共 Subversion 项目导入 Git,GitHub 可以帮忙。在指南的最后有一个指向导入 Subversion 页面的链接。点击它会看到一个表单,包含有关导入流程的信息以及一个用来粘贴公共 Subversion 项目连接的文本框(见图 4-9):



图 4-9. Subversion 导入界面
如果项目很大,采用非标准结构,或者是私有的,那就无法借助该工具实现导入。到第 7 章,我们会介绍如何手工导入复杂工程的具体方法。

添加协作开发者
现在把团队里的其他人也加进来。如果 John,Josie 和 Jessica 都在 GitHub 注册了账户,要赋予他们对该仓库的推送权限,可以把他们加为项目协作者。这样他们就可以通过各自的公钥访问我的这个仓库了。

点击项目页面上方的 “edit” 按钮或者顶部的 Admin 标签,进入该项目的管理页面(见图 4-10):



图 4-10. GitHub 的项目管理页面
为了给另一个用户添加项目的写权限,点击 “Add another collaborator” 链接,出现一个用于输入用户名的表单。在输入的同时,它会自动跳出一个符合条件的候选名单。找到正确用户名之后,点 Add 按钮,把该用户设为项目协作者(见图 4-11):



图 4-11. 为项目添加协作者
添加完协作者之后,就可以在 Repository Collaborators 区域看到他们的名单(见图 4-12):



图 4-12. 项目协作者名单
如果要取消某人的访问权,点击 “revoke” 即可取消他的推送权限。对于将来的项目,你可以从现有项目复制协作者名单,或者直接借用协作者群组。

项目页面
在推送或从 Subversion 导入项目之后,你会看到一个类似图 4-13 的项目主页:



图 4-13. GitHub 上的项目主页
别人访问你的项目时看到的就是这个页面。它有若干导航标签,Commits 标签用于显示提交历史,最新的提交位于最上方,这和 git log 命令的输出类似。Network 标签展示所有派生了该项目并做出贡献的用户的关系图谱。Downloads 标签允许你上传项目的二进制文件,提供下载该项目各个版本的 tar/zip 包。Wiki 标签提供了一个用于撰写文档或其他项目相关信息的 wiki 站点。Graphs 标签包含了一些可视化的项目信息与数据。默认打开的 Source 标签页面,则列出了该项目的目录结构和概要信息,并在下方自动展示 README 文件的内容(如果该文件存在的话),此外还会显示最近一次提交的相关信息。

派生项目
如果要为一个自己没有推送权限的项目贡献代码,GitHub 鼓励使用派生(fork)。到那个感兴趣的项目主页上,点击页面上方的 “fork” 按钮,GitHub 就会为你复制一份该项目的副本到你的仓库中,这样你就可以向自己的这个副本推送数据了。

采取这种办法的好处是,项目拥有者不必忙于应付赋予他人推送权限的工作。随便谁都可以通过派生得到一个项目副本并在其中展开工作,事后只需要项目维护者将这些副本仓库加为远程仓库,然后提取更新合并即可。

要派生一个项目,到原始项目的页面(本例中是 mojombo/chronic)点击 “fork” 按钮(见图 4-14):



图 4-14. 点击 “fork” 按钮获得任意项目的可写副本
几秒钟之后,你将进入新建的项目页面,会显示该项目派生自哪一个项目(见图 4-15):



图 4-15. 派生后得到的项目副本
GitHub 小结
关于 GitHub 就先介绍这么多,能够快速达成这些事情非常重要(译注:门槛的降低和完成基本任务的简单高效,对于推动开源项目的协作发展有着举足轻重的意义。)。短短几 分钟内,你就能创建一个新账户,添加一个项目并开始推送。如果项目是开源的,整个庞大的开发者社区都可以立即访问它,提供各式各样的帮助和贡献。最起码, 这也是一种 Git 新手立即体验尝试 Git 的捷径。



4.11  小结
我们讨论并介绍了一些建立远程 Git 仓库的方法,接下来你可以通过这些仓库同他人分享或合作。

运行自己的服务器意味着更多的控制权以及在防火墙内部操作的可能性,当然这样的服务器通常需要投入一定的时间精力来架设维护。如果直接托管,虽然能免去这部分工作,但有时出于安全或版权的考虑,有些公司禁止将商业代码托管到第三方服务商。

所以究竟采取哪种方案,并不是个难以取舍的问题,或者其一,或者相互配合,哪种合适就用哪种。

作者: compare2000    时间: 2014-05-22 17:36
Git详解五: 分布式Git  
为了便于项目中的所有开发者分享代码,我们准备好了一台服务器存放远程 Git 仓库。经过前面几章的学习,我们已经学会了一些基本的本地工作流程中所需用到的命令。接下来,我们要学习下如何利用 Git 来组织和完成分布式工作流程。

特别是,当作为项目贡献者时,我们该怎么做才能方便维护者采纳更新;或者作为项目维护者时,又该怎样有效管理大量贡献者的提交。





5.1  分布式工作流程
同传统的集中式版本控制系统(CVCS)不同,开发者之间的协作方式因着 Git 的分布式特性而变得更为灵活多样。在集中式系统上,每个开发者就像是连接在集线器上的节点,彼此的工作方式大体相像。而在 Git 网络中,每个开发者同时扮演着节点和集线器的角色,这就是说,每一个开发者都可以将自己的代码贡献到另外一个开发者的仓库中,或者建立自己的公共仓库,让 其他开发者基于自己的工作开始,为自己的仓库贡献代码。于是,Git 的分布式协作便可以衍生出种种不同的工作流程,我会在接下来的章节介绍几种常见的应用方式,并分别讨论各自的优缺点。你可以选择其中的一种,或者结合起 来,应用到你自己的项目中。

集中式工作流
通常,集中式工作流程使用的都是单点协作模型。一个存放代码仓库的中心服务器,可以接受所有开发者提交的代码。所有的开发者都是普通的节点,作为中心集线器的消费者,平时的工作就是和中心仓库同步数据(见图 5-1)。



图 5-1. 集中式工作流
如果两个开发者从中心仓库克隆代码下来,同时作了一些修订,那么只有第一个开发者可以顺利地把数据推送到共享服务器。第二个开发者在提交他的修订之 前,必须先下载合并服务器上的数据,解决冲突之后才能推送数据到共享服务器上。在 Git 中这么用也决无问题,这就好比是在用 Subversion(或其他 CVCS)一样,可以很好地工作。

如果你的团队不是很大,或者大家都已经习惯了使用集中式工作流程,完全可以采用这种简单的模式。只需要配置好一台中心服务器,并给每个人推送数据的 权限,就可以开展工作了。但如果提交代码时有冲突, Git 根本就不会让用户覆盖他人代码,它直接驳回第二个人的提交操作。这就等于告诉提交者,你所作的修订无法通过快近(fast-forward)来合并,你必 须先拉取最新数据下来,手工解决冲突合并后,才能继续推送新的提交。绝大多数人都熟悉和了解这种模式的工作方式,所以使用也非常广泛。

集成管理员工作流
由于 Git 允许使用多个远程仓库,开发者便可以建立自己的公共仓库,往里面写数据并共享给他人,而同时又可以从别人的仓库中提取他们的更新过来。这种情形通常都会有 个代表着官方发布的项目仓库(blessed repository),开发者们由此仓库克隆出一个自己的公共仓库(developer public),然后将自己的提交推送上去,请求官方仓库的维护者拉取更新合并到主项目。维护者在自己的本地也有个克隆仓库(integration manager),他可以将你的公共仓库作为远程仓库添加进来,经过测试无误后合并到主干分支,然后再推送到官方仓库。工作流程看起来就像图 5-2 所示:

1.项目维护者可以推送数据到公共仓库 blessed repository。 2. 贡献者克隆此仓库,修订或编写新代码。
2.贡献者推送数据到自己的公共仓库 developer public。 4. 贡献者给维护者发送邮件,请求拉取自己的最新修订。
3.维护者在自己本地的 integration manger 仓库中,将贡献者的仓库加为远程仓库,合并更新并做测试。
4.维护者将合并后的更新推送到主仓库 blessed repository。


图 5-2. 集成管理员工作流
在 GitHub 网站上使用得最多的就是这种工作流。人们可以复制(fork 亦即克隆)某个项目到自己的列表中,成为自己的公共仓库。随后将自己的更新提交到这个仓库,所有人都可以看到你的每次更新。这么做最主要的优点在于,你可 以按照自己的节奏继续工作,而不必等待维护者处理你提交的更新;而维护者也可以按照自己的节奏,任何时候都可以过来处理接纳你的贡献。

司令官与副官工作流
这其实是上一种工作流的变体。一般超大型的项目才会用到这样的工作方式,像是拥有数百协作开发者的 Linux 内核项目就是如此。各个集成管理员分别负责集成项目中的特定部分,所以称为副官(lieutenant)。而所有这些集成管理员头上还有一位负责统筹的总 集成管理员,称为司令官(dictator)。司令官维护的仓库用于提供所有协作者拉取最新集成的项目代码。整个流程看起来如图 5-3 所示:

1.一般的开发者在自己的特性分支上工作,并不定期地根据主干分支(dictator 上的 master)衍合。
2.副官(lieutenant)将普通开发者的特性分支合并到自己的 master 分支中。
3.司令官(dictator)将所有副官的 master 分支并入自己的 master 分支。
4.司令官(dictator)将集成后的 master 分支推送到共享仓库 blessed repository 中,以便所有其他开发者以此为基础进行衍合。



图 5-3. 司令官与副官工作流
这种工作流程并不常用,只有当项目极为庞杂,或者需要多级别管理时,才会体现出优势。利用这种方式,项目总负责人(即司令官)可以把大量分散的集成工作委托给不同的小组负责人分别处理,最后再统筹起来,如此各人的职责清晰明确,也不易出错(译注:此乃分而治之)。

以上介绍的是常见的分布式系统可以应用的工作流程,当然不止于 Git。在实际的开发工作中,你可能会遇到各种为了满足特定需求而有所变化的工作方式。我想现在你应该已经清楚,接下来自己需要用哪种方式开展工作了。下 节我还会再举些例子,看看各式工作流中的每个角色具体应该如何操作。



5.2  为项目作贡献
接下来,我们来学习一下作为项目贡献者,会有哪些常见的工作模式。

不过要说清楚整个协作过程真的很难,Git 如此灵活,人们的协作方式便可以各式各样,没有固定不变的范式可循,而每个项目的具体情况又多少会有些不同,比如说参与者的规模,所选择的工作流程,每个人的提交权限,以及 Git 以外贡献等等,都会影响到具体操作的细节。

首当其冲的是参与者规模。项目中有多少开发者是经常提交代码的?经常又是多久呢?大多数两至三人的小团队,一天大约只有几次提交,如果不是什么热门 项目的话就更少了。可要是在大公司里,或者大项目中,参与者可以多到上千,每天都会有十几个上百个补丁提交上来。这种差异带来的影响是显著的,越是多的人 参与进来,就越难保证每次合并正确无误。你正在工作的代码,可能会因为合并进来其他人的更新而变得过时,甚至受创无法运行。而已经提交上去的更新,也可能 在等着审核合并的过程中变得过时。那么,我们该怎样做才能确保代码是最新的,提交的补丁也是可用的呢?

接下来便是项目所采用的工作流。是集中式的,每个开发者都具有等同的写权限?项目是否有专人负责检查所有补丁?是不是所有补丁都做过同行复阅(peer-review)再通过审核的?你是否参与审核过程?如果使用副官系统,那你是不是限定于只能向此副官提交?

还有你的提交权限。有或没有向主项目提交更新的权限,结果完全不同,直接决定最终采用怎样的工作流。如果不能直接提交更新,那该如何贡献自己的代码呢?是不是该有个什么策略?你每次贡献代码会有多少量?提交频率呢?

所有以上这些问题都会或多或少影响到最终采用的工作流。接下来,我会在一系列由简入繁的具体用例中,逐一阐述。此后在实践时,应该可以借鉴这里的例子,略作调整,以满足实际需要构建自己的工作流。

提交指南
开始分析特定用例之前,先来了解下如何撰写提交说明。一份好的提交指南可以帮助协作者更轻松更有效地配合。Git 项目本身就提供了一份文档(Git 项目源代码目录中Documentation/SubmittingPatches),列数了大量提示,从如何编撰提交说明到提交补丁,不一而足。

首先,请不要在更新中提交多余的白字符(whitespace)。Git 有种检查此类问题的方法,在提交之前,先运行 git diff --check,会把可能的多余白字符修正列出来。下面的示例,我已经把终端中显示为红色的白字符用X 替换掉:

$ git diff --checklib/simplegit.rb:5: trailing whitespace.+    @git_dir = File.expand_path(git_dir)XXlib/simplegit.rb:7: trailing whitespace.+ XXXXXXXXXXXlib/simplegit.rb:26: trailing whitespace.+    def command(git_cmd)XXXX这样在提交之前你就可以看到这类问题,及时解决以免困扰其他开发者。

接下来,请将每次提交限定于完成一次逻辑功能。并且可能的话,适当地分解为多次小更新,以便每次小型提交都更易于理解。请不要在周末穷追猛打一次性 解决五个问题,而最后拖到周一再提交。就算是这样也请尽可能利用暂存区域,将之前的改动分解为每次修复一个问题,再分别提交和加注说明。如果针对两个问题 改动的是同一个文件,可以试试看git add --patch 的方式将部分内容置入暂存区域(我们会在第六章再详细介绍)。无论是五次小提交还是混杂在一起的大提交,最终分支末端的项目快照应该还是一样的,但分解开 来之后,更便于其他开发者复阅。这么做也方便自己将来取消某个特定问题的修复。我们将在第六章介绍一些重写提交历史,同暂存区域交互的技巧和工具,以便最 终得到一个干净有意义,且易于理解的提交历史。

最后需要谨记的是提交说明的撰写。写得好可以让大家协作起来更轻松。一般来说,提交说明最好限制在一行以内,50 个字符以下,简明扼要地描述更新内容,空开一行后,再展开详细注解。Git 项目本身需要开发者撰写详尽注解,包括本次修订的因由,以及前后不同实现之间的比较,我们也该借鉴这种做法。另外,提交说明应该用祈使现在式语态,比如, 不要说成 “I added tests for” 或 “Adding tests for” 而应该用 “Add tests for”。下面是来自 tpope.net 的 Tim Pope 原创的提交说明格式模版,供参考:

本次更新的简要描述(50 个字符以内)如果必要,此处展开详尽阐述。段落宽度限定在 72 个字符以内。某些情况下,第一行的简要描述将用作邮件标题,其余部分作为邮件正文。其间的空行是必要的,以区分两者(当然没有正文另当别论)。如果并在一起,rebase 这样的工具就可能会迷惑。另起空行后,再进一步补充其他说明。 - 可以使用这样的条目列举式。 - 一般以单个空格紧跟短划线或者星号作为每项条目的起始符。每个条目间用一空行隔开。   不过这里按自己项目的约定,可以略作变化。如果你的提交说明都用这样的格式来书写,好多事情就可以变得十分简单。Git 项目本身就是这样要求的,我强烈建议你到 Git 项目仓库下运行 git log --no-merges 看看,所有提交历史的说明是怎样撰写的。(译注:如果现在还没有克隆 git 项目源代码,是时候git clone git://git.kernel.org/pub/scm/git/git.git 了。)

为简单起见,在接下来的例子(及本书随后的所有演示)中,我都不会用这种格式,而使用 -m 选项提交 git commit。不过请还是按照我之前讲的做,别学我这里偷懒的方式。

私有的小型团队
我们从最简单的情况开始,一个私有项目,与你一起协作的还有另外一到两位开发者。这里说私有,是指源代码不公开,其他人无法访问项目仓库。而你和其他开发者则都具有推送数据到仓库的权限。

这种情况下,你们可以用 Subversion 或其他集中式版本控制系统类似的工作流来协作。你仍然可以得到 Git 带来的其他好处:离线提交,快速分支与合并等等,但工作流程还是差不多的。主要区别在于,合并操作发生在客户端而非服务器上。让我们来看看,两个开发者一 起使用同一个共享仓库,会发生些什么。第一个人,John,克隆了仓库,作了些更新,在本地提交。(下面的例子中省略了常规提示,用... 代替以节约版面。)

# John's Machine$ git clone john@githost:simplegit.gitInitialized empty Git repository in /home/john/simplegit/.git/...$ cd simplegit/$ vim lib/simplegit.rb $ git commit -am 'removed invalid default value'[master 738ee87] removed invalid default value 1 files changed, 1 insertions(+), 1 deletions(-)第二个开发者,Jessica,一样这么做:克隆仓库,提交更新:

# Jessica's Machine$ git clone jessica@githost:simplegit.gitInitialized empty Git repository in /home/jessica/simplegit/.git/...$ cd simplegit/$ vim TODO $ git commit -am 'add reset task'[master fbff5bc] add reset task 1 files changed, 1 insertions(+), 0 deletions(-)现在,Jessica 将她的工作推送到服务器上:

# Jessica's Machine$ git push origin master...To jessica@githost:simplegit.git   1edee6b..fbff5bc  master -> masterJohn 也尝试推送自己的工作上去:

# John's Machine$ git push origin masterTo john@githost:simplegit.git ! [rejected]        master -> master (non-fast forward)error: failed to push some refs to 'john@githost:simplegit.git'John 的推送操作被驳回,因为 Jessica 已经推送了新的数据上去。请注意,特别是你用惯了 Subversion 的话,这里其实修改的是两个文件,而不是同一个文件的同一个地方。Subversion 会在服务器端自动合并提交上来的更新,而 Git 则必须先在本地合并后才能推送。于是,John 不得不先把 Jessica 的更新拉下来:

$ git fetch origin...From john@githost:simplegit + 049d078...fbff5bc master     -> origin/master此刻,John 的本地仓库如图 5-4 所示:



图 5-4. John 的仓库历史
虽然 John 下载了 Jessica 推送到服务器的最近更新(fbff5),但目前只是 origin/master 指针指向它,而当前的本地分支master 仍然指向自己的更新(738ee),所以需要先把她的提交合并过来,才能继续推送数据:

$ git merge origin/masterMerge made by recursive. TODO |    1 + 1 files changed, 1 insertions(+), 0 deletions(-)还好,合并过程非常顺利,没有冲突,现在 John 的提交历史如图 5-5 所示:



图 5-5. 合并 origin/master 后 John 的仓库历史
现在,John 应该再测试一下代码是否仍然正常工作,然后将合并结果(72bbc)推送到服务器上:

$ git push origin master...To john@githost:simplegit.git   fbff5bc..72bbc59  master -> master最终,John 的提交历史变为图 5-6 所示:



图 5-6. 推送后 John 的仓库历史
而在这段时间,Jessica 已经开始在另一个特性分支工作了。她创建了 issue54 并提交了三次更新。她还没有下载 John 提交的合并结果,所以提交历史如图 5-7 所示:



图 5-7. Jessica 的提交历史
Jessica 想要先和服务器上的数据同步,所以先下载数据:

# Jessica's Machine$ git fetch origin...From jessica@githost:simplegit   fbff5bc..72bbc59  master     -> origin/master于是 Jessica 的本地仓库历史多出了 John 的两次提交(738ee 和 72bbc),如图 5-8 所示:



图 5-8. 获取 John 的更新之后 Jessica 的提交历史
此时,Jessica 在特性分支上的工作已经完成,但她想在推送数据之前,先确认下要并进来的数据究竟是什么,于是运行 git log 查看:

$ git log --no-merges origin/master ^issue54 commit 738ee872852dfaa9d6634e0dea7a324040193016 Author: John Smith Date: Fri May 29 16:01:27 2009 -0700 removed invalid default value现在,Jessica 可以将特性分支上的工作并到 master 分支,然后再并入 John 的工作(origin/master)到自己的master 分支,最后再推送回服务器。当然,得先切回主分支才能集成所有数据:

$ git checkout masterSwitched to branch "master"Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.要合并 origin/master 或 issue54 分支,谁先谁后都没有关系,因为它们都在上游(upstream)(译注:想像分叉的更新像是汇流成河的源头,所以上游 upstream 是指最新的提交),所以无所谓先后顺序,最终合并后的内容快照都是一样的,而仅是提交历史看起来会有些先后差别。Jessica 选择先合并issue54:

$ git merge issue54Updating fbff5bc..4af4298Fast forward README           |    1 + lib/simplegit.rb |    6 +++++- 2 files changed, 6 insertions(+), 1 deletions(-)正如所见,没有冲突发生,仅是一次简单快进。现在 Jessica 开始合并 John 的工作(origin/master):

$ git merge origin/masterAuto-merging lib/simplegit.rbMerge made by recursive. lib/simplegit.rb |    2 +- 1 files changed, 1 insertions(+), 1 deletions(-)所有的合并都非常干净。现在 Jessica 的提交历史如图 5-9 所示:



图 5-9. 合并 John 的更新后 Jessica 的提交历史
现在 Jessica 已经可以在自己的 master 分支中访问 origin/master 的最新改动了,所以她应该可以成功推送最后的合并结果到服务器上(假设 John 此时没再推送新数据上来):

$ git push origin master...To jessica@githost:simplegit.git   72bbc59..8059c15  master -> master至此,每个开发者都提交了若干次,且成功合并了对方的工作成果,最新的提交历史如图 5-10 所示:



图 5-10. Jessica 推送数据后的提交历史
以上就是最简单的协作方式之一:先在自己的特性分支中工作一段时间,完成后合并到自己的 master 分支;然后下载合并 origin/master 上的更新(如果有的话),再推回远程服务器。一般的协作流程如图 5-11 所示:



图 5-11. 多用户共享仓库协作方式的一般工作流程时序
私有团队间协作
现在我们来看更大一点规模的私有团队协作。如果有几个小组分头负责若干特性的开发和集成,那他们之间的协作过程是怎样的。

假设 John 和 Jessica 一起负责开发某项特性 A,而同时 Jessica 和 Josie 一起负责开发另一项功能 B。公司使用典型的集成管理员式工作流,每个组都有一名管理员负责集成本组代码,及更新项目主仓库的master 分支。所有开发都在代表小组的分支上进行。

让我们跟随 Jessica 的视角看看她的工作流程。她参与开发两项特性,同时和不同小组的开发者一起协作。克隆生成本地仓库后,她打算先着手开发特性 A。于是创建了新的featureA 分支,继而编写代码:

# Jessica's Machine$ git checkout -b featureASwitched to a new branch "featureA"$ vim lib/simplegit.rb$ git commit -am 'add limit to log function'[featureA 3300904] add limit to log function 1 files changed, 1 insertions(+), 1 deletions(-)此刻,她需要分享目前的进展给 John,于是她将自己的 featureA 分支提交到服务器。由于 Jessica 没有权限推送数据到主仓库的master 分支(只有集成管理员有此权限),所以只能将此分支推上去同 John 共享协作:

$ git push origin featureA...To jessica@githost:simplegit.git * [new branch]      featureA -> featureAJessica 发邮件给 John 让他上来看看 featureA 分支上的进展。在等待他的反馈之前,Jessica 决定继续工作,和 Josie 一起开发featureB 上的特性 B。当然,先创建此分支,分叉点以服务器上的 master 为起点:

# Jessica's Machine$ git fetch origin$ git checkout -b featureB origin/masterSwitched to a new branch "featureB"随后,Jessica 在 featureB 上提交了若干更新:

$ vim lib/simplegit.rb$ git commit -am 'made the ls-tree function recursive'[featureB e5b0fdc] made the ls-tree function recursive 1 files changed, 1 insertions(+), 1 deletions(-)$ vim lib/simplegit.rb$ git commit -am 'add ls-files'[featureB 8512791] add ls-files 1 files changed, 5 insertions(+), 0 deletions(-)现在 Jessica 的更新历史如图 5-12 所示:



图 5-12. Jessica 的更新历史
Jessica 正准备推送自己的进展上去,却收到 Josie 的来信,说是她已经将自己的工作推到服务器上的 featureBee 分支了。这样,Jessica 就必须先将 Josie 的代码合并到自己本地分支中,才能再一起推送回服务器。她用git fetch 下载 Josie 的最新代码:

$ git fetch origin...From jessica@githost:simplegit * [new branch]      featureBee -> origin/featureBee然后 Jessica 使用 git merge 将此分支合并到自己分支中:

$ git merge origin/featureBeeAuto-merging lib/simplegit.rbMerge made by recursive. lib/simplegit.rb |    4 ++++ 1 files changed, 4 insertions(+), 0 deletions(-)合并很顺利,但另外有个小问题:她要推送自己的 featureB 分支到服务器上的 featureBee 分支上去。当然,她可以使用冒号(:)格式指定目标分支:

$ git push origin featureB:featureBee...To jessica@githost:simplegit.git   fba9af8..cd685d1  featureB -> featureBee我们称此为_refspec_。更多有关于 Git refspec 的讨论和使用方式会在第九章作详细阐述。

接下来,John 发邮件给 Jessica 告诉她,他看了之后作了些修改,已经推回服务器 featureA 分支,请她过目下。于是 Jessica 运行git fetch 下载最新数据:

$ git fetch origin...From jessica@githost:simplegit   3300904..aad881d  featureA   -> origin/featureA接下来便可以用 git log 查看更新了些什么:

$ git log origin/featureA ^featureA commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6 Author: John Smith Date: Fri May 29 19:57:33 2009 -0700 changed log output to 30 from 25最后,她将 John 的工作合并到自己的 featureA 分支中:

$ git checkout featureASwitched to branch "featureA"$ git merge origin/featureAUpdating 3300904..aad881dFast forward lib/simplegit.rb |   10 +++++++++-1 files changed, 9 insertions(+), 1 deletions(-)Jessica 稍做一番修整后同步到服务器:

$ git commit -am 'small tweak'[featureA ed774b3] small tweak 1 files changed, 1 insertions(+), 1 deletions(-)$ git push origin featureA...To jessica@githost:simplegit.git   3300904..ed774b3  featureA -> featureA现在的 Jessica 提交历史如图 5-13 所示:



图 5-13. 在特性分支中提交更新后的提交历史
现在,Jessica,Josie 和 John 通知集成管理员服务器上的 featureA 及 featureBee 分支已经准备好,可以并入主线了。在管理员完成集成工作后,主分支上便多出一个新的合并提交(5399e),用 fetch 命令更新到本地后,提交历史如图 5-14 所示:



图 5-14. 合并特性分支后的 Jessica 提交历史
许多开发小组改用 Git 就是因为它允许多个小组间并行工作,而在稍后恰当时机再行合并。通过共享远程分支的方式,无需干扰整体项目代码便可以开展工作,因此使用 Git 的小型团队间协作可以变得非常灵活自由。以上工作流程的时序如图 5-15 所示:



图 5-15. 团队间协作工作流程基本时序
公开的小型项目
上面说的是私有项目协作,但要给公开项目作贡献,情况就有些不同了。因为你没有直接更新主仓库分支的权限,得寻求其它方式把工作成果交给项目维护 人。下面会介绍两种方法,第一种使用 git 托管服务商提供的仓库复制功能,一般称作 fork,比如 repo.or.cz 和 GitHub 都支持这样的操作,而且许多项目管理员都希望大家使用这样的方式。另一种方法是通过电子邮件寄送文件补丁。

但不管哪种方式,起先我们总需要克隆原始仓库,而后创建特性分支开展工作。基本工作流程如下:

$ git clone (url)$ cd project$ git checkout -b featureA$ (work)$ git commit$ (work)$ git commit你可能想到用 rebase -i 将所有更新先变作单个提交,又或者想重新安排提交之间的差异补丁,以方便项目维护者审阅 – 有关交互式衍合操作的细节见第六章。

在完成了特性分支开发,提交给项目维护者之前,先到原始项目的页面上点击“Fork”按钮,创建一个自己可写的公共仓库(译注:即下面的 url 部分,参照后续的例子,应该是git://githost/simplegit.git)。然后将此仓库添加为本地的第二个远端仓库,姑且称为 myfork:

$ git remote add myfork (url)你需要将本地更新推送到这个仓库。要是将远端 master 合并到本地再推回去,还不如把整个特性分支推上去来得干脆直接。而且,假若项目维护者未采纳你的贡献的话(不管是直接合并还是 cherry pick),都不用回退(rewind)自己的 master 分支。但若维护者合并或 cherry-pick 了你的工作,最后总还可以从他们的更新中同步这些代码。好吧,现在先把 featureA 分支整个推上去:

$ git push myfork featureA然后通知项目管理员,让他来抓取你的代码。通常我们把这件事叫做 pull request。可以直接用 GitHub 等网站提供的 “pull request” 按钮自动发送请求通知;或手工把git request-pull 命令输出结果电邮给项目管理员。

request-pull 命令接受两个参数,第一个是本地特性分支开始前的原始分支,第二个是请求对方来抓取的 Git 仓库 URL(译注:即下面myfork 所指的,自己可写的公共仓库)。比如现在Jessica 准备要给 John 发一个 pull requst,她之前在自己的特性分支上提交了两次更新,并把分支整个推到了服务器上,所以运行该命令会看到:

$ git request-pull origin/master myforkThe following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40:  John Smith (1):        added a new functionare available in the git repository at:  git://githost/simplegit.git featureAJessica Smith (2):      add limit to log function      change log output to 30 from 25 lib/simplegit.rb |   10 +++++++++- 1 files changed, 9 insertions(+), 1 deletions(-)输出的内容可以直接发邮件给管理者,他们就会明白这是从哪次提交开始旁支出去的,该到哪里去抓取新的代码,以及新的代码增加了哪些功能等等。

像这样随时保持自己的 master 分支和官方 origin/master 同步,并将自己的工作限制在特性分支上的做法,既方便又灵活,采纳和丢弃都轻而易举。就算原始主干发生变化,我们也能重新衍合提供新的补丁。比如现在要开始第二项特性的开发,不要在原来已推送的特性分支上继续,还是按原始master 开始:

$ git checkout -b featureB origin/master$ (work)$ git commit$ git push myfork featureB$ (email maintainer)$ git fetch origin现在,A、B 两个特性分支各不相扰,如同竹筒里的两颗豆子,队列中的两个补丁,你随时都可以分别从头写过,或者衍合,或者修改,而不用担心特性代码的交叉混杂。如图 5-16 所示:



图 5-16. featureB 以后的提交历史
假设项目管理员接纳了许多别人提交的补丁后,准备要采纳你提交的第一个分支,却发现因为代码基准不一致,合并工作无法正确干净地完成。这就需要你再次衍合到最新的 origin/master,解决相关冲突,然后重新提交你的修改:

$ git checkout featureA$ git rebase origin/master$ git push -f myfork featureA自然,这会重写提交历史,如图 5-17 所示:



图 5-17. featureA 重新衍合后的提交历史
注意,此时推送分支必须使用 -f 选项(译注:表示 force,不作检查强制重写)替换远程已有的 featureA 分支,因为新的 commit 并非原来的后续更新。当然你也可以直接推送到另一个新的分支上去,比如称作featureAv2。

再考虑另一种情形:管理员看过第二个分支后觉得思路新颖,但想请你改下具体实现。我们只需以当前 origin/master 分支为基准,开始一个新的特性分支featureBv2,然后把原来的 featureB 的更新拿过来,解决冲突,按要求重新实现部分代码,然后将此特性分支推送上去:

$ git checkout -b featureBv2 origin/master$ git merge --no-commit --squash featureB$ (change implementation)$ git commit$ git push myfork featureBv2这里的 --squash 选项将目标分支上的所有更改全拿来应用到当前分支上,而 --no-commit 选项告诉 Git 此时无需自动生成和记录(合并)提交。这样,你就可以在原来代码基础上,继续工作,直到最后一起提交。

好了,现在可以请管理员抓取 featureBv2 上的最新代码了,如图 5-18 所示:



图 5-18. featureBv2 之后的提交历史
公开的大型项目
许多大型项目都会立有一套自己的接受补丁流程,你应该注意下其中细节。但多数项目都允许通过开发者邮件列表接受补丁,现在我们来看具体例子。

整个工作流程类似上面的情形:为每个补丁创建独立的特性分支,而不同之处在于如何提交这些补丁。不需要创建自己可写的公共仓库,也不用将自己的更新推送到自己的服务器,你只需将每次提交的差异内容以电子邮件的方式依次发送到邮件列表中即可。

$ git checkout -b topicA$ (work)$ git commit$ (work)$ git commit如此一番后,有了两个提交要发到邮件列表。我们可以用 git format-patch 命令来生成 mbox 格式的文件然后作为附件发送。每个提交都会封装为一个.patch 后缀的 mbox 文件,但其中只包含一封邮件,邮件标题就是提交消息(译注:额外有前缀,看例子),邮件内容包含补丁正文和 Git 版本号。这种方式的妙处在于接受补丁时仍可保留原来的提交消息,请看接下来的例子:

$ git format-patch -M origin/master0001-add-limit-to-log-function.patch0002-changed-log-output-to-30-from-25.patchformat-patch 命令依次创建补丁文件,并输出文件名。上面的 -M 选项允许 Git 检查是否有对文件重命名的提交。我们来看看补丁文件的内容:

$ cat 0001-add-limit-to-log-function.patch From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001 From: Jessica Smith Date: Sun, 6 Apr 2008 10:17:23 -0700 Subject: [PATCH 1/2] add limit to log function Limit log functionality to the first 20 --- lib/simplegit.rb | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/lib/simplegit.rb b/lib/simplegit.rb index 76f47bc..f9815f1 100644 --- a/lib/simplegit.rb +++ b/lib/simplegit.rb @@ -14,7 +14,7 @@ class SimpleGit end def log(treeish = 'master') - command("git log #{treeish}") + command("git log -n 20 #{treeish}") end def ls_tree(treeish = 'master') -- 1.6.2.rc1.20.g8c5b.dirty如果有额外信息需要补充,但又不想放在提交消息中说明,可以编辑这些补丁文件,在第一个 --- 行之前添加说明,但不要修改下面的补丁正文,比如例子中的Limit log functionality to the first 20 部分。这样,其它开发者能阅读,但在采纳补丁时不会将此合并进来。

你可以用邮件客户端软件发送这些补丁文件,也可以直接在命令行发送。有些所谓智能的邮件客户端软件会自作主张帮你调整格式,所以粘贴补丁到邮件正文 时,有可能会丢失换行符和若干空格。Git 提供了一个通过 IMAP 发送补丁文件的工具。接下来我会演示如何通过 Gmail 的 IMAP 服务器发送。另外,在 Git 源代码中有个Documentation/SubmittingPatches 文件,可以仔细读读,看看其它邮件程序的相关导引。

首先在 ~/.gitconfig 文件中配置 imap 项。每个选项都可用 git config 命令分别设置,当然直接编辑文件添加以下内容更便捷:

[imap]  folder = "[Gmail]/Drafts"  host = imaps://imap.gmail.com  user = user@gmail.com  pass = p4ssw0rd  port = 993  sslverify = false如果你的 IMAP 服务器没有启用 SSL,就无需配置最后那两行,并且 host 应该以 imap:// 开头而不再是有 s 的 imaps://。保存配置文件后,就能用 git send-email 命令把补丁作为邮件依次发送到指定的 IMAP 服务器上的文件夹中(译注:这里就是 Gmail 的[Gmail]/Drafts 文件夹。但如果你的语言设置不是英文,此处的文件夹 Drafts 字样会变为对应的语言。):

$ git send-email *.patch 0001-added-limit-to-log-function.patch 0002-changed-log-output-to-30-from-25.patch Who should the emails appear to be from? [Jessica Smith ] Emails will be sent from: Jessica Smith Who should the emails be sent to? jessica@example.com Message-ID to be used as In-Reply-To for the first email? y  接下来,Git 会根据每个补丁依次输出类似下面的日志:

(mbox) Adding cc: Jessica Smith from \line 'From: Jessica Smith ' OK. Log says: Sendmail: /usr/sbin/sendmail -i jessica@example.com From: Jessica Smith To: jessica@example.com Subject: [PATCH 1/2] added limit to log function Date: Sat, 30 May 2009 13:29:15 -0700 Message-Id: <1243715356-61726-1-git-send-email-jessica@example.com> X-Mailer: git-send-email 1.6.2.rc1.20.g8c5b.dirty In-Reply-To: References: Result: OK     最后,到 Gmail 上打开 Drafts 文件夹,编辑这些邮件,修改收件人地址为邮件列表地址,另外给要抄送的人也加到 Cc 列表中,最后发送。

小结
本节主要介绍了常见 Git 项目协作的工作流程,还有一些帮助处理这些工作的命令和工具。接下来我们要看看如何维护 Git 项目,并成为一个合格的项目管理员,或是集成经理。

作者: compare2000    时间: 2014-05-22 17:36
5.3  项目的管理
既然是相互协作,在贡献代码的同时,也免不了要维护管理自己的项目。像是怎么处理别人用 format-patch 生成的补丁,或是集成远端仓库上某个分支上的变化等等。但无论是管理代码仓库,还是帮忙审核收到的补丁,都需要同贡献者约定某种长期可持续的工作方式。

使用特性分支进行工作
如果想要集成新的代码进来,最好局限在特性分支上做。临时的特性分支可以让你随意尝试,进退自如。比如碰上无法正常工作的补丁,可以先搁在那边,直到有时间仔细核查修复为止。创建的分支可以用相关的主题关键字命名,比如ruby_client 或者其它类似的描述性词语,以帮助将来回忆。Git 项目本身还时常把分支名称分置于不同命名空间下,比如 sc/ruby_client 就说明这是 sc 这个人贡献的。现在从当前主干分支为基础,新建临时分支:

$ git branch sc/ruby_client master另外,如果你希望立即转到分支上去工作,可以用 checkout -b:

$ git checkout -b sc/ruby_client master好了,现在已经准备妥当,可以试着将别人贡献的代码合并进来了。之后评估一下有没有问题,最后再决定是不是真的要并入主干。

采纳来自邮件的补丁
如果收到一个通过电邮发来的补丁,你应该先把它应用到特性分支上进行评估。有两种应用补丁的方法:git apply 或者 git am。

使用 apply 命令应用补丁
如果收到的补丁文件是用 git diff 或由其它 Unix 的 diff 命令生成,就该用 git apply 命令来应用补丁。假设补丁文件存在 /tmp/patch-ruby-client.patch,可以这样运行:

$ git apply /tmp/patch-ruby-client.patch这会修改当前工作目录下的文件,效果基本与运行 patch -p1 打补丁一样,但它更为严格,且不会出现混乱。如果是 git diff 格式描述的补丁,此命令还会相应地添加,删除,重命名文件。当然,普通的patch 命令是不会这么做的。另外请注意,git apply 是一个事务性操作的命令,也就是说,要么所有补丁都打上去,要么全部放弃。所以不会出现patch 命令那样,一部分文件打上了补丁而另一部分却没有,这样一种不上不下的修订状态。所以总的来说,git apply 要比patch 严谨许多。因为仅仅是更新当前的文件,所以此命令不会自动生成提交对象,你得手工缓存相应文件的更新状态并执行提交命令。

在实际打补丁之前,可以先用 git apply --check 查看补丁是否能够干净顺利地应用到当前分支中:

$ git apply --check 0001-seeing-if-this-helps-the-gem.patch error: patch failed: ticgit.gemspec:1error: ticgit.gemspec: patch does not apply如果没有任何输出,表示我们可以顺利采纳该补丁。如果有问题,除了报告错误信息之外,该命令还会返回一个非零的状态,所以在 shell 脚本里可用于检测状态。

使用 am 命令应用补丁
如果贡献者也用 Git,且擅于制作 format-patch 补丁,那你的合并工作将会非常轻松。因为这些补丁中除了文件内容差异外,还包含了作者信息和提交消息。所以请鼓励贡献者用format-patch 生成补丁。对于传统的 diff 命令生成的补丁,则只能用 git apply 处理。

对于 format-patch 制作的新式补丁,应当使用 git am 命令。从技术上来说,git am 能够读取 mbox 格式的文件。这是种简单的纯文本文件,可以包含多封电邮,格式上用 From 加空格以及随便什么辅助信息所组成的行作为分隔行,以区分每封邮件,就像这样:

From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001 From: Jessica Smith Date: Sun, 6 Apr 2008 10:17:23 -0700 Subject: [PATCH 1/2] add limit to log function Limit log functionality to the first 20这是 format-patch 命令输出的开头几行,也是一个有效的 mbox 文件格式。如果有人用 git send-email 给你发了一个补丁,你可以将此邮件下载到本地,然后运行git am 命令来应用这个补丁。如果你的邮件客户端能将多封电邮导出为 mbox 格式的文件,就可以用 git am 一次性应用所有导出的补丁。

如果贡献者将 format-patch 生成的补丁文件上传到类似 Request Ticket 一样的任务处理系统,那么可以先下载到本地,继而使用git am 应用该补丁:

$ git am 0001-limit-log-function.patch Applying: add limit to log function你会看到它被干净地应用到本地分支,并自动创建了新的提交对象。作者信息取自邮件头 From 和 Date,提交消息则取自Subject 以及正文中补丁之前的内容。来看具体实例,采纳之前展示的那个 mbox 电邮补丁后,最新的提交对象为:

$ git log --pretty=fuller -1 commit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0 Author: Jessica Smith AuthorDate: Sun Apr 6 10:17:23 2008 -0700 Commit: Scott Chacon CommitDate: Thu Apr 9 09:19:06 2009 -0700 add limit to log function Limit log functionality to the first 20  Commit 部分显示的是采纳补丁的人,以及采纳的时间。而 Author 部分则显示的是原作者,以及创建补丁的时间。

有时,我们也会遇到打不上补丁的情况。这多半是因为主干分支和补丁的基础分支相差太远,但也可能是因为某些依赖补丁还未应用。这种情况下,git am 会报错并询问该怎么做:

$ git am 0001-seeing-if-this-helps-the-gem.patch Applying: seeing if this helps the gemerror: patch failed: ticgit.gemspec:1error: ticgit.gemspec: patch does not applyPatch failed at 0001.When you have resolved this problem run "git am --resolved".If you would prefer to skip this patch, instead run "git am --skip".To restore the original branch and stop patching run "git am --abort".Git 会在有冲突的文件里加入冲突解决标记,这同合并或衍合操作一样。解决的办法也一样,先编辑文件消除冲突,然后暂存文件,最后运行 git am --resolved 提交修正结果:

$ (fix the file)$ git add ticgit.gemspec $ git am --resolvedApplying: seeing if this helps the gem如果想让 Git 更智能地处理冲突,可以用 -3 选项进行三方合并。如果当前分支未包含该补丁的基础代码或其祖先,那么三方合并就会失败,所以该选项默认为关闭状态。一般来说,如果该补丁是基于某个公开的提交制作而成的话,你总是可以通过同步来获取这个共同祖先,所以用三方合并选项可以解决很多麻烦:

$ git am -3 0001-seeing-if-this-helps-the-gem.patch Applying: seeing if this helps the gemerror: patch failed: ticgit.gemspec:1error: ticgit.gemspec: patch does not applyUsing index info to reconstruct a base tree...Falling back to patching base and 3-way merge...No changes -- Patch already applied.像上面的例子,对于打过的补丁我又再打一遍,自然会产生冲突,但因为加上了 -3 选项,所以它很聪明地告诉我,无需更新,原有的补丁已经应用。

对于一次应用多个补丁时所用的 mbox 格式文件,可以用 am 命令的交互模式选项 -i,这样就会在打每个补丁前停住,询问该如何操作:

$ git am -3 -i mboxCommit Body is:--------------------------seeing if this helps the gem--------------------------Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all 在多个补丁要打的情况下,这是个非常好的办法,一方面可以预览下补丁内容,同时也可以有选择性的接纳或跳过某些补丁。

打完所有补丁后,如果测试下来新特性可以正常工作,那就可以安心地将当前特性分支合并到长期分支中去了。

检出远程分支
如果贡献者有自己的 Git 仓库,并将修改推送到此仓库中,那么当你拿到仓库的访问地址和对应分支的名称后,就可以加为远程分支,然后在本地进行合并。

比如,Jessica 发来一封邮件,说在她代码库中的 ruby-client 分支上已经实现了某个非常棒的新功能,希望我们能帮忙测试一下。我们可以先把她的仓库加为远程仓库,然后抓取数据,完了再将她所说的分支检出到本地来测试:

$ git remote add jessica git://github.com/jessica/myproject.git$ git fetch jessica$ git checkout -b rubyclient jessica/ruby-client若是不久她又发来邮件,说还有个很棒的功能实现在另一分支上,那我们只需重新抓取下最新数据,然后检出那个分支到本地就可以了,无需重复设置远程仓库。

这种做法便于同别人保持长期的合作关系。但前提是要求贡献者有自己的服务器,而我们也需要为每个人建一个远程分支。有些贡献者提交代码补丁并不是很 频繁,所以通过邮件接收补丁效率会更高。同时我们自己也不会希望建上百来个分支,却只从每个分支取一两个补丁。但若是用脚本程序来管理,或直接使用代码仓 库托管服务,就可以简化此过程。当然,选择何种方式取决于你和贡献者的喜好。

使用远程分支的另外一个好处是能够得到提交历史。不管代码合并是不是会有问题,至少我们知道该分支的历史分叉点,所以默认会从共同祖先开始自动进行三方合并,无需 -3 选项,也不用像打补丁那样祈祷存在共同的基准点。

如果只是临时合作,只需用 git pull 命令抓取远程仓库上的数据,合并到本地临时分支就可以了。一次性的抓取动作自然不会把该仓库地址加为远程仓库。

$ git pull git://github.com/onetimeguy/project.gitFrom git://github.com/onetimeguy/project * branch            HEAD       -> FETCH_HEADMerge made by recursive.决断代码取舍
现在特性分支上已合并好了贡献者的代码,是时候决断取舍了。本节将回顾一些之前学过的命令,以看清将要合并到主干的是哪些代码,从而理解它们到底做了些什么,是否真的要并入。

一般我们会先看下,特性分支上都有哪些新增的提交。比如在 contrib 特性分支上打了两个补丁,仅查看这两个补丁的提交信息,可以用--not 选项指定要屏蔽的分支 master,这样就会剔除重复的提交历史:

$ git log contrib --not master commit 5b6235bd297351589efc4d73316f0a68d484f118 Author: Scott Chacon Date: Fri Oct 24 09:53:59 2008 -0700 seeing if this helps the gem commit 7482e0d16d04bea79d0dba8988cc78df655f16a0 Author: Scott Chacon Date: Mon Oct 22 19:38:36 2008 -0700 updated the gemspec to hopefully work better  还可以查看每次提交的具体修改。请牢记,在 git log 后加 -p 选项将展示每次提交的内容差异。

如果想看当前分支同其他分支合并时的完整内容差异,有个小窍门:

$ git diff master虽然能得到差异内容,但请记住,结果有可能和我们的预期不同。一旦主干 master 在特性分支创建之后有所修改,那么通过 diff 命令来比较的,是最新主干上的提交快照。显然,这不是我们所要的。比方在 master 分支中某个文件里添了一行,然后运行上面的命令,简单的比较最新快照所得到的结论只能是,特性分支中删除了这一行。

这个很好理解:如果 master 是特性分支的直接祖先,不会产生任何问题;如果它们的提交历史在不同的分叉上,那么产生的内容差异,看起来就像是增加了特性分支上的新代码,同时删除了master 分支上的新代码。

实际上我们真正想要看的,是新加入到特性分支的代码,也就是合并时会并入主干的代码。所以,准确地讲,我们应该比较特性分支和它同 master 分支的共同祖先之间的差异。

我们可以手工定位它们的共同祖先,然后与之比较:

$ git merge-base contrib master36c7dba2c95e6bbb78dfa822519ecfec6e1ca649$ git diff 36c7db 但这么做很麻烦,所以 Git 提供了便捷的 ... 语法。对于 diff 命令,可以把 ... 加在原始分支(拥有共同祖先)和当前分支之间:

$ git diff master...contrib现在看到的,就是实际将要引入的新代码。这是一个非常有用的命令,应该牢记。

代码集成
一旦特性分支准备停当,接下来的问题就是如何集成到更靠近主线的分支中。此外还要考虑维护项目的总体步骤是什么。虽然有很多选择,不过我们这里只介绍其中一部分。

合并流程
一般最简单的情形,是在 master 分支中维护稳定代码,然后在特性分支上开发新功能,或是审核测试别人贡献的代码,接着将它并入主干,最后删除这个特性分支,如此反复。来看示例,假设当前代码库中有两个分支,分别为ruby_client 和 php_client,如图 5-19 所示。然后先把 ruby_client 合并进主干,再合并php_client,最后的提交历史如图 5-20 所示。



图 5-19. 多个特性分支


图 5-20. 合并特性分支之后
这是最简单的流程,所以在处理大一些的项目时可能会有问题。

对于大型项目,至少需要维护两个长期分支 master 和 develop。新代码(图 5-21 中的 ruby_client)将首先并入 develop 分支(图 5-22 中的 C8),经过一个阶段,确认develop 中的代码已稳定到可发行时,再将 master 分支快进到稳定点(图 5-23 中的 C8)。而平时这两个分支都会被推送到公开的代码库。



图 5-21. 特性分支合并前


图 5-22. 特性分支合并后


图 5-23. 特性分支发布后
这样,在人们克隆仓库时就有两种选择:既可检出最新稳定版本,确保正常使用;也能检出开发版本,试用最前沿的新特性。你也可以扩展这个概念,先将所有新代码合并到临时特性分支,等到该分支稳定下来并通过测试后,再并入develop 分支。然后,让时间检验一切,如果这些代码确实可以正常工作相当长一段时间,那就有理由相信它已经足够稳定,可以放心并入主干分支发布。

大项目的合并流程
Git 项目本身有四个长期分支:用于发布的 master 分支、用于合并基本稳定特性的 next 分支、用于合并仍需改进特性的pu 分支(pu 是 proposed updates 的缩写),以及用于除错维护的 maint 分支(maint 取自 maintenance)。维护者可以按照之前介绍的方法,将贡献者的代码引入为不同的特性分支(如图 5-24 所示),然后测试评估,看哪些特性能稳定工作,哪些还需改进。稳定的特性可以并入next 分支,然后再推送到公共仓库,以供其他人试用。



图 5-24. 管理复杂的并行贡献
仍需改进的特性可以先并入 pu 分支。直到它们完全稳定后再并入 master。同时一并检查下 next 分支,将足够稳定的特性也并入 master。所以一般来说,master 始终是在快进,next 偶尔做下衍合,而pu 则是频繁衍合,如图 5-25 所示:



图 5-25. 将特性并入长期分支
并入 master 后的特性分支,已经无需保留分支索引,放心删除好了。Git 项目还有一个 maint 分支,它是以最近一次发行版为基础分化而来的,用于维护除错补丁。所以克隆 Git 项目仓库后会得到这四个分支,通过检出不同分支可以了解各自进展,或是试用前沿特性,或是贡献代码。而维护者则通过管理这些分支,逐步有序地并入第三方贡献。

衍合与挑拣(cherry-pick)的流程
一些维护者更喜欢衍合或者挑拣贡献者的代码,而不是简单的合并,因为这样能够保持线性的提交历史。如果你完成了一个特性的开发,并决定将它引入到主干代码中,你可以转到那个特性分支然后执行衍合命令,好在你的主干分支上(也可能是develop分支之类的)重新提交这些修改。如果这些代码工作得很好,你就可以快进master分支,得到一个线性的提交历史。

另一个引入代码的方法是挑拣。挑拣类似于针对某次特定提交的衍合。它首先提取某次提交的补丁,然后试着应用在当前分支上。如果某个特性分支上有多个 commits,但你只想引入其中之一就可以使用这种方法。也可能仅仅是因为你喜欢用挑拣,讨厌衍合。假设你有一个类似图 5-26 的工程。



图 5-26. 挑拣(cherry-pick)之前的历史
如果你希望拉取e43a6到你的主干分支,可以这样:

$ git cherry-pick e43a6fd3e94888d76779ad79fb568ed180e5fcdfFinished one cherry-pick.[master]: created a0a41a9: "More friendly message when locking the index fails." 3 files changed, 17 insertions(+), 3 deletions(-)这将会引入e43a6的代码,但是会得到不同的SHA-1值,因为应用日期不同。现在你的历史看起来像图 5-27.



图 5-27. 挑拣(cherry-pick)之后的历史
现在,你可以删除这个特性分支并丢弃你不想引入的那些commit。

给发行版签名
你可以删除上次发布的版本并重新打标签,也可以像第二章所说的那样建立一个新的标签。如果你决定以维护者的身份给发行版签名,应该这样做:

$ git tag -s v1.5 -m 'my signed 1.5 tag' You need a passphrase to unlock the secret key for user: "Scott Chacon " 1024-bit DSA key, ID F721C45A, created 2009-02-09完成签名之后,如何分发PGP公钥(public key)是个问题。(译者注:分发公钥是为了验证标签)。还好,Git的设计者想到了解决办法:可以把key(既公钥)作为blob变量写入Git库,然后把它的内容直接写在标签里。gpg --list-keys命令可以显示出你所拥有的key:

$ gpg --list-keys /Users/schacon/.gnupg/pubring.gpg --------------------------------- pub 1024D/F721C45A 2009-02-09 [expires: 2010-02-09] uid Scott Chacon sub 2048g/45D02282 2009-02-09 [expires: 2010-02-09]然后,导出key的内容并经由管道符传递给git hash-object,之后钥匙会以blob类型写入Git中,最后返回这个blob量的SHA-1值:

$ gpg -a --export F721C45A | git hash-object -w --stdin659ef797d181633c87ec71ac3f9ba29fe5775b92现在你的Git已经包含了这个key的内容了,可以通过不同的SHA-1值指定不同的key来创建标签。

$ git tag -a maintainer-pgp-pub 659ef797d181633c87ec71ac3f9ba29fe5775b92在运行git push --tags命令之后,maintainer-pgp-pub标签就会公布给所有人。如果有人想要校验标签,他可以使用如下命令导入你的key:

$ git show maintainer-pgp-pub | gpg --import人们可以用这个key校验你签名的所有标签。另外,你也可以在标签信息里写入一个操作向导,用户只需要运行git show 查看标签信息,然后按照你的向导就能完成校验。

生成内部版本号
因为Git不会为每次提交自动附加类似’v123’的递增序列,所以如果你想要得到一个便于理解的提交号可以运行git describe命令。Git将会返回一个字符串,由三部分组成:最近一次标定的版本号,加上自那次标定之后的提交次数,再加上一段SHA-1值of the commit you’re describing:

$ git describe masterv1.6.2-rc1-20-g8c5b85c这个字符串可以作为快照的名字,方便人们理解。如果你的Git是你自己下载源码然后编译安装的,你会发现git --version命令的输出和这个字符串差不多。如果在一个刚刚打完标签的提交上运行describe命令,只会得到这次标定的版本号,而没有后面两项信息。

git describe命令只适用于有标注的标签(通过-a或者-s选项创建的标签),所以发行版的标签都应该是带有标注的,以保证git describe能够正确的执行。你也可以把这个字符串作为checkout或者show命令的目标,因为他们最终都依赖于一个简短的SHA-1值,当然如果这个SHA-1值失效他们也跟着失效。最近Linux内核为了保证SHA-1值的唯一性,将位数由8位扩展到10位,这就导致扩展之前的git describe输出完全失效了。

准备发布
现在可以发布一个新的版本了。首先要将代码的压缩包归档,方便那些可怜的还没有使用Git的人们。可以使用git archive:

$ git archive master --prefix='project/' | gzip > `git describe master`.tar.gz$ ls *.tar.gzv1.6.2-rc1-20-g8c5b85c.tar.gz这个压缩包解压出来的是一个文件夹,里面是你项目的最新代码快照。你也可以用类似的方法建立一个zip压缩包,在git archive加上--format=zip选项:

$ git archive master --prefix='project/' --format=zip > `git describe master`.zip现在你有了一个tar.gz压缩包和一个zip压缩包,可以把他们上传到你网站上或者用e-mail发给别人。

制作简报
是时候通知邮件列表里的朋友们来检验你的成果了。使用git shortlog命令可以方便快捷的制作一份修改日志(changelog),告诉大家上次发布之后又增加了哪些特性和修复了哪些bug。实际上这个命令能够统计给定范围内的所有提交;假如你上一次发布的版本是v1.0.1,下面的命令将给出自从上次发布之后的所有提交的简介:

$ git shortlog --no-merges master --not v1.0.1Chris Wanstrath (:      Add support for annotated tags to Grit::Tag      Add packed-refs annotated tag support.      Add Grit::Commit#to_patch      Update version and History.txt      Remove stray `puts`      Make ls_tree ignore nilsTom Preston-Werner (4):      fix dates in history      dynamic version method      Version bump to 1.0.2      Regenerated gemspec for version 1.0.2这就是自从v1.0.1版本以来的所有提交的简介,内容按照作者分组,以便你能快速的发e-mail给他们。



5.4  小结
你学会了如何使用Git为项目做贡献,也学会了如何使用Git维护你的项目。恭喜!你已经成为一名高效的开发者。在下一篇你将学到更强大的工具来处理更加复杂的问题,之后你会变成一位Git大师。

作者: compare2000    时间: 2014-05-22 17:37
Git详解六:Git工具  
Git 工具
现在,你已经学习了管理或者维护 Git 仓库,实现代码控制所需的大多数日常命令和工作流程。你已经完成了跟踪和提交文件的基本任务,并且发挥了暂存区和轻量级的特性分支及合并的威力。

接下来你将领略到一些 Git 可以实现的非常强大的功能,这些功能你可能并不会在日常操作中使用,但在某些时候你也许会需要。



6.1  修订版本(Revision)选择
Git 允许你通过几种方法来指明特定的或者一定范围内的提交。了解它们并不是必需的,但是了解一下总没坏处。

单个修订版本
显然你可以使用给出的 SHA-1 值来指明一次提交,不过也有更加人性化的方法来做同样的事。本节概述了指明单个提交的诸多方法。

简短的SHA
Git 很聪明,它能够通过你提供的前几个字符来识别你想要的那次提交,只要你提供的那部分 SHA-1 不短于四个字符,并且没有歧义——也就是说,当前仓库中只有一个对象以这段 SHA-1 开头。

例如,想要查看一次指定的提交,假设你运行 git log 命令并找到你增加了功能的那次提交:

$ git logcommit 734713bc047d87bf7eac9674765ae793478c50d3Author: Scott Chacon <schacon@gmail.com>Date:   Fri Jan 2 18:32:33 2009 -0800    fixed refs handling, added gc auto, updated testscommit d921970aadf03b3cf0e71becdaab3147ba71cdefMerge: 1c002dd... 35cfb2b...Author: Scott Chacon <schacon@gmail.com>Date:   Thu Dec 11 15:08:43 2008 -0800    Merge commit 'phedders/rdocs'commit 1c002dd4b536e7479fe34593e72e6c6c1819e53bAuthor: Scott Chacon <schacon@gmail.com>Date:   Thu Dec 11 14:58:32 2008 -0800    added some blame and merge stuff假设是 1c002dd.... 。如果你想 git show 这次提交,下面的命令是等价的(假设简短的版本没有歧义):

$ git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b$ git show 1c002dd4b536e7479f$ git show 1c002dGit 可以为你的 SHA-1 值生成出简短且唯一的缩写。如果你传递 --abbrev-commit 给 git log 命令,输出结果里就会使用简短且唯一的值;它默认使用七个字符来表示,不过必要时为了避免 SHA-1 的歧义,会增加字符数:

$ git log --abbrev-commit --pretty=onelineca82a6d changed the version number085bb3b removed unnecessary test codea11bef0 first commit通常在一个项目中,使用八到十个字符来避免 SHA-1 歧义已经足够了。最大的 Git 项目之一,Linux 内核,目前也只需要最长 40 个字符中的 12 个字符来保持唯一性。

关于 SHA-1 的简短说明
许多人可能会担心一个问题:在随机的偶然情况下,在他们的仓库里会出现两个具有相同 SHA-1 值的对象。那会怎么样呢?

如果你真的向仓库里提交了一个跟之前的某个对象具有相同 SHA-1 值的对象,Git 将会发现之前的那个对象已经存在在 Git 数据库中,并认为它已经被写入了。如果什么时候你想再次检出那个对象时,你会总是得到先前的那个对象的数据。

不过,你应该了解到,这种情况发生的概率是多么微小。SHA-1 摘要长度是 20 字节,也就是 160 位。为了保证有 50% 的概率出现一次冲突,需要 2^80 个随机哈希的对象(计算冲突机率的公式是p = (n(n-1)/2) * (1/2^160))。2^80 是 1.2 x 10^24,也就是一亿亿亿,那是地球上沙粒总数的 1200 倍。

现在举例说一下怎样才能产生一次 SHA-1 冲突。如果地球上 65 亿的人类都在编程,每人每秒都在产生等价于整个 Linux 内核历史(一百万个 Git 对象)的代码,并将之提交到一个巨大的 Git 仓库里面,那将花费 5 年的时间才会产生足够的对象,使其拥有 50% 的概率产生一次 SHA-1 对象冲突。这要比你编程团队的成员同一个晚上在互不相干的意外中被狼袭击并杀死的机率还要小。

分支引用
指明一次提交的最直接的方法要求有一个指向它的分支引用。这样,你就可以在任何需要一个提交对象或者 SHA-1 值的 Git 命令中使用该分支名称了。如果你想要显示一个分支的最后一次提交的对象,例如假设topic1 分支指向 ca82a6d,那么下面的命令是等价的:

$ git show ca82a6dff817ec66f44342007202690a93763949$ git show topic1如果你想知道某个分支指向哪个特定的 SHA,或者想看任何一个例子中被简写的 SHA-1,你可以使用一个叫做 rev-parse 的 Git 探测工具。在第 9 章你可以看到关于探测工具的更多信息;简单来说,rev-parse 是为了底层操作而不是日常操作设计的。不过,有时你想看 Git 现在到底处于什么状态时,它可能会很有用。这里你可以对你的分支运执行rev-parse。

$ git rev-parse topic1ca82a6dff817ec66f44342007202690a93763949引用日志里的简称
在你工作的同时,Git 在后台的工作之一就是保存一份引用日志——一份记录最近几个月你的 HEAD 和分支引用的日志。

你可以使用 git reflog 来查看引用日志:

$ git reflog734713b... HEAD@{0}: commit: fixed refs handling, added gc auto, updatedd921970... HEAD@{1}: merge phedders/rdocs: Merge made by recursive.1c002dd... HEAD@{2}: commit: added some blame and merge stuff1c36188... HEAD@{3}: rebase -i (squash): updating HEAD95df984... HEAD@{4}: commit: # This is a combination of two commits.1c36188... HEAD@{5}: rebase -i (squash): updating HEAD7e05da5... HEAD@{6}: rebase -i (pick): updating HEAD每次你的分支顶端因为某些原因被修改时,Git 就会为你将信息保存在这个临时历史记录里面。你也可以使用这份数据来指明更早的分支。如果你想查看仓库中 HEAD 在五次前的值,你可以使用引用日志的输出中的@{n} 引用:

$ git show HEAD@{5}你也可以使用这个语法来查看一定时间前分支指向哪里。例如,想看你的 master 分支昨天在哪,你可以输入

$ git show master@{yesterday}它就会显示昨天分支的顶端在哪。这项技术只对还在你引用日志里的数据有用,所以不能用来查看比几个月前还早的提交。

想要看类似于 git log 输出格式的引用日志信息,你可以运行 git log -g:

$ git log -g mastercommit 734713bc047d87bf7eac9674765ae793478c50d3Reflog: master@{0} (Scott Chacon <schacon@gmail.com>)Reflog message: commit: fixed refs handling, added gc auto, updated Author: Scott Chacon <schacon@gmail.com>Date:   Fri Jan 2 18:32:33 2009 -0800    fixed refs handling, added gc auto, updated testscommit d921970aadf03b3cf0e71becdaab3147ba71cdefReflog: master@{1} (Scott Chacon <schacon@gmail.com>)Reflog message: merge phedders/rdocs: Merge made by recursive.Author: Scott Chacon <schacon@gmail.com>Date:   Thu Dec 11 15:08:43 2008 -0800    Merge commit 'phedders/rdocs'需要注意的是,日志引用信息只存在于本地——这是一个你在仓库里做过什么的日志。其他人的仓库拷贝里的引用和你的相同;而你新克隆一个仓库的时候,引用日志是空的,因为你在仓库里还没有操作。只有你克隆了一个项目至少两个月,git show HEAD@{2.months.ago} 才会有用——如果你是五分钟前克隆的仓库,将不会有结果返回。

祖先引用
另一种指明某次提交的常用方法是通过它的祖先。如果你在引用最后加上一个 ^,Git 将其理解为此次提交的父提交。 假设你的工程历史是这样的:

$ git log --pretty=format:'%h %s' --graph* 734713b fixed refs handling, added gc auto, updated tests*   d921970 Merge commit 'phedders/rdocs'|\  | * 35cfb2b Some rdoc changes* | 1c002dd added some blame and merge stuff|/  * 1c36188 ignore *.gem* 9b29157 add open3_detach to gemspec file list那么,想看上一次提交,你可以使用 HEAD^,意思是“HEAD 的父提交”:

$ git show HEAD^commit d921970aadf03b3cf0e71becdaab3147ba71cdefMerge: 1c002dd... 35cfb2b...Author: Scott Chacon <schacon@gmail.com>Date:   Thu Dec 11 15:08:43 2008 -0800    Merge commit 'phedders/rdocs'你也可以在 ^ 后添加一个数字——例如,d921970^2 意思是“d921970 的第二父提交”。这种语法只在合并提交时有用,因为合并提交可能有多个父提交。第一父提交是你合并时所在分支,而第二父提交是你所合并的分支:

$ git show d921970^commit 1c002dd4b536e7479fe34593e72e6c6c1819e53bAuthor: Scott Chacon <schacon@gmail.com>Date:   Thu Dec 11 14:58:32 2008 -0800    added some blame and merge stuff$ git show d921970^2commit 35cfb2b795a55793d7cc56a6cc2060b4bb732548Author: Paul Hedderly <paul+git@mjr.org>Date:   Wed Dec 10 22:22:03 2008 +0000    Some rdoc changes另外一个指明祖先提交的方法是 ~。这也是指向第一父提交,所以 HEAD~ 和 HEAD^ 是等价的。当你指定数字的时候就明显不一样了。HEAD~2 是指“第一父提交的第一父提交”,也就是“祖父提交”——它会根据你指定的次数检索第一父提交。例如,在上面列出的历史记录里面,HEAD~3 会是

$ git show HEAD~3commit 1c3618887afb5fbcbea25b7c013f4e2114448b8dAuthor: Tom Preston-Werner <tom@mojombo.com>Date:   Fri Nov 7 13:47:59 2008 -0500    ignore *.gem也可以写成 HEAD^^^,同样是第一父提交的第一父提交的第一父提交:

$ git show HEAD^^^commit 1c3618887afb5fbcbea25b7c013f4e2114448b8dAuthor: Tom Preston-Werner <tom@mojombo.com>Date:   Fri Nov 7 13:47:59 2008 -0500    ignore *.gem你也可以混合使用这些语法——你可以通过 HEAD~3^2 指明先前引用的第二父提交(假设它是一个合并提交)。

提交范围
现在你已经可以指明单次的提交,让我们来看看怎样指明一定范围的提交。这在你管理分支的时候尤显重要——如果你有很多分支,你可以指明范围来圈定一些问题的答案,比如:“这个分支上我有哪些工作还没合并到主分支的?”

双点
最常用的指明范围的方法是双点的语法。这种语法主要是让 Git 区分出可从一个分支中获得而不能从另一个分支中获得的提交。例如,假设你有类似于图 6-1 的提交历史。



图 6-1. 范围选择的提交历史实例
你想要查看你的试验分支上哪些没有被提交到主分支,那么你就可以使用 master..experiment 来让 Git 显示这些提交的日志——这句话的意思是“所有可从experiment分支中获得而不能从master分支中获得的提交”。为了使例子简单明了,我使用了图标中提交对象的字母来代替真实日志的输出,所以会显示:

$ git log master..experimentDC另一方面,如果你想看相反的——所有在 master 而不在 experiment 中的分支——你可以交换分支的名字。experiment..master 显示所有可在master 获得而在 experiment 中不能的提交:

$ git log experiment..masterFE这在你想保持 experiment 分支最新和预览你将合并的提交的时候特别有用。这个语法的另一种常见用途是查看你将把什么推送到远程:

$ git log origin/master..HEAD这条命令显示任何在你当前分支上而不在远程origin 上的提交。如果你运行 git push 并且的你的当前分支正在跟踪origin/master,被git log origin/master..HEAD 列出的提交就是将被传输到服务器上的提交。 你也可以留空语法中的一边来让 Git 来假定它是 HEAD。例如,输入git log origin/master.. 将得到和上面的例子一样的结果—— Git 使用 HEAD 来代替不存在的一边。

多点
双点语法就像速记一样有用;但是你也许会想针对两个以上的分支来指明修订版本,比如查看哪些提交被包含在某些分支中的一个,但是不在你当前的分支上。Git允许你在引用前使用^字符或者--not指明你不希望提交被包含其中的分支。因此下面三个命令是等同的:

$ git log refA..refB$ git log ^refA refB$ git log refB --not refA这样很好,因为它允许你在查询中指定多于两个的引用,而这是双点语法所做不到的。例如,如果你想查找所有从refA或refB包含的但是不被refC包含的提交,你可以输入下面中的一个

$ git log refA refB ^refC$ git log refA refB --not refC这建立了一个非常强大的修订版本查询系统,应该可以帮助你解决分支里包含了什么这个问题。

三点
最后一种主要的范围选择语法是三点语法,这个可以指定被两个引用中的一个包含但又不被两者同时包含的分支。回过头来看一下图6-1里所列的提交历史的例子。 如果你想查看master或者experiment中包含的但不是两者共有的引用,你可以运行

$ git log master...experimentFEDC这个再次给出你普通的log输出但是只显示那四次提交的信息,按照传统的提交日期排列。

这种情形下,log命令的一个常用参数是--left-right,它会显示每个提交到底处于哪一侧的分支。这使得数据更加有用。

$ git log --left-right master...experiment< F< E> D> C有了以上工具,让Git知道你要察看哪些提交就容易得多了。



6.2  交互式暂存
Git提供了很多脚本来辅助某些命令行任务。这里,你将看到一些交互式命令,它们帮助你方便地构建只包含特定组合和部分文件的提交。在你修改了一大批文件然后决定将这些变更分布在几个各有侧重的提交而不是单个又大又乱的提交时,这些工具非常有用。用这种方法,你可以确保你的提交在逻辑上划分为相应的变更集,以便于供和你一起工作的开发者审阅。如果你运行git add时加上-i或者--interactive选项,Git就进入了一个交互式的shell模式,显示一些类似于下面的信息:

$ git add -i           staged     unstaged path  1:    unchanged        +0/-1 TODO  2:    unchanged        +1/-1 index.html  3:    unchanged        +5/-1 lib/simplegit.rb*** Commands ***  1: status     2: update      3: revert     4: add untracked  5: patch      6: diff        7: quit       8: helpWhat now> 你会看到这个命令以一个完全不同的视图显示了你的暂存区——主要是你通过git status得到的那些信息但是稍微简洁但信息更加丰富一些。它在左侧列出了你暂存的变更,在右侧列出了未被暂存的变更。

在这之后是一个命令区。这里你可以做很多事情,包括暂存文件,撤回文件,暂存部分文件,加入未被追踪的文件,查看暂存文件的差别。

暂存和撤回文件
如果你在What now>的提示后输入2或者u,这个脚本会提示你那些文件你想要暂存:

What now> 2           staged     unstaged path  1:    unchanged        +0/-1 TODO  2:    unchanged        +1/-1 index.html  3:    unchanged        +5/-1 lib/simplegit.rbUpdate>>如果想暂存TODO和index.html,你可以输入相应的编号:

Update>> 1,2           staged     unstaged path* 1:    unchanged        +0/-1 TODO* 2:    unchanged        +1/-1 index.html  3:    unchanged        +5/-1 lib/simplegit.rbUpdate>>每个文件旁边的*表示选中的文件将被暂存。如果你在update>>提示后直接敲入回车,Git会替你把所有选中的内容暂存:

Update>> updated 2 paths*** Commands ***  1: status     2: update      3: revert     4: add untracked  5: patch      6: diff        7: quit       8: helpWhat now> 1           staged     unstaged path  1:        +0/-1      nothing TODO  2:        +1/-1      nothing index.html  3:    unchanged        +5/-1 lib/simplegit.rb现在你可以看到TODO和index.html文件被暂存了同时simplegit.rb文件仍然未被暂存。如果这时你想要撤回TODO文件,就使用3或者r(代表revert,恢复)选项:

*** Commands ***  1: status     2: update      3: revert     4: add untracked  5: patch      6: diff        7: quit       8: helpWhat now> 3           staged     unstaged path  1:        +0/-1      nothing TODO  2:        +1/-1      nothing index.html  3:    unchanged        +5/-1 lib/simplegit.rbRevert>> 1           staged     unstaged path* 1:        +0/-1      nothing TODO  2:        +1/-1      nothing index.html  3:    unchanged        +5/-1 lib/simplegit.rbRevert>> [enter]reverted one path再次查看Git的状态,你会看到你已经撤回了TODO文件

*** Commands ***  1: status     2: update      3: revert     4: add untracked  5: patch      6: diff        7: quit       8: helpWhat now> 1           staged     unstaged path  1:    unchanged        +0/-1 TODO  2:        +1/-1      nothing index.html  3:    unchanged        +5/-1 lib/simplegit.rb要查看你暂存内容的差异,你可以使用6或者d(表示diff)命令。它会显示你暂存文件的列表,你可以选择其中的几个,显示其被暂存的差异。这跟你在命令行下指定git diff --cached非常相似:

*** Commands ***  1: status     2: update      3: revert     4: add untracked  5: patch      6: diff        7: quit       8: helpWhat now> 6           staged     unstaged path  1:        +1/-1      nothing index.htmlReview diff>> 1diff --git a/index.html b/index.htmlindex 4d07108..4335f49 100644--- a/index.html+++ b/index.html@@ -16,7 +16,7 @@ Date Finder <p id="out">...</p>-<div id="footer">contact : support@github.com</div>+<div id="footer">contact : email.support@github.com</div> <script type="text/javascript">通过这些基本命令,你可以使用交互式增加模式更加方便地处理暂存区。

暂存补丁
只让Git暂存文件的某些部分而忽略其他也是有可能的。例如,你对simplegit.rb文件作了两处修改但是只想暂存其中一个而忽略另一个,在Git中实现这一点非常容易。在交互式的提示符下,输入5或者p(表示patch,补丁)。Git会询问哪些文件你希望部分暂存;然后对于被选中文件的每一节,他会逐个显示文件的差异区块并询问你是否希望暂存他们:

diff --git a/lib/simplegit.rb b/lib/simplegit.rbindex dd5ecc4..57399e0 100644--- a/lib/simplegit.rb+++ b/lib/simplegit.rb@@ -22,7 +22,7 @@ class SimpleGit   end   def log(treeish = 'master')-    command("git log -n 25 #{treeish}")+    command("git log -n 30 #{treeish}")   end   def blame(path)Stage this hunk [y,n,a,d,/,j,J,g,e,?]? 此处你有很多选择。输入?可以显示列表:

Stage this hunk [y,n,a,d,/,j,J,g,e,?]? ?y - stage this hunkn - do not stage this hunka - stage this and all the remaining hunks in the filed - do not stage this hunk nor any of the remaining hunks in the fileg - select a hunk to go to/ - search for a hunk matching the given regexj - leave this hunk undecided, see next undecided hunkJ - leave this hunk undecided, see next hunkk - leave this hunk undecided, see previous undecided hunkK - leave this hunk undecided, see previous hunks - split the current hunk into smaller hunkse - manually edit the current hunk? - print help如果你想暂存各个区块,通常你会输入y或者n,但是暂存特定文件里的全部区块或者暂时跳过对一个区块的处理同样也很有用。如果你暂存了文件的一个部分而保留另外一个部分不被暂存,你的状态输出看起来会是这样:

What now> 1           staged     unstaged path  1:    unchanged        +0/-1 TODO  2:        +1/-1      nothing index.html  3:        +1/-1        +4/-0 lib/simplegit.rbsimplegit.rb的状态非常有意思。它显示有几行被暂存了,有几行没有。你部分地暂存了这个文件。在这时,你可以退出交互式脚本然后运行git commit来提交部分暂存的文件。

最后你也可以不通过交互式增加的模式来实现部分文件暂存——你可以在命令行下通过git add -p或者git add --patch来启动同样的脚本。

作者: compare2000    时间: 2014-05-22 17:38
6.3  储藏(Stashing)
经常有这样的事情发生,当你正在进行项目中某一部分的工作,里面的东西处于一个比较杂乱的状态,而你想转到其他分支上进行一些工作。问题是,你不想提交进行了一半的工作,否则以后你无法回到这个工作点。解决这个问题的办法就是git stash命令。

“‘储藏”“可以获取你工作目录的中间状态——也就是你修改过的被追踪的文件和暂存的变更——并将它保存到一个未完结变更的堆栈中,随时可以重新应用。

储藏你的工作
为了演示这一功能,你可以进入你的项目,在一些文件上进行工作,有可能还暂存其中一个变更。如果你运行 git status,你可以看到你的中间状态:

$ git status# On branch master# Changes to be committed:#   (use "git reset HEAD <file>..." to unstage)##      modified:   index.html## Changed but not updated:#   (use "git add <file>..." to update what will be committed)##      modified:   lib/simplegit.rb#现在你想切换分支,但是你还不想提交你正在进行中的工作;所以你储藏这些变更。为了往堆栈推送一个新的储藏,只要运行 git stash:

$ git stashSaved working directory and index state \  "WIP on master: 049d078 added the index file"HEAD is now at 049d078 added the index file(To restore them type "git stash apply"你的工作目录就干净了:

$ git status# On branch masternothing to commit (working directory clean)这时,你可以方便地切换到其他分支工作;你的变更都保存在栈上。要查看现有的储藏,你可以使用 git stash list:

$ git stash liststash@{0}: WIP on master: 049d078 added the index filestash@{1}: WIP on master: c264051... Revert "added file_size"stash@{2}: WIP on master: 21d80a5... added number to log在这个案例中,之前已经进行了两次储藏,所以你可以访问到三个不同的储藏。你可以重新应用你刚刚实施的储藏,所采用的命令就是之前在原始的 stash 命令的帮助输出里提示的:git stash apply。如果你想应用更早的储藏,你可以通过名字指定它,像这样:git stash apply stash@{2}。如果你不指明,Git 默认使用最近的储藏并尝试应用它:

$ git stash apply# On branch master# Changed but not updated:#   (use "git add <file>..." to update what will be committed)##      modified:   index.html#      modified:   lib/simplegit.rb#你可以看到 Git 重新修改了你所储藏的那些当时尚未提交的文件。在这个案例里,你尝试应用储藏的工作目录是干净的,并且属于同一分支;但是一个干净的工作目录和应用到相同的分支上并不是应用储藏的必要条件。你可以在其中一个分支上保留一份储藏,随后切换到另外一个分支,再重新应用这些变更。在工作目录里包含已修改和未提交的文件时,你也可以应用储藏——Git 会给出归并冲突如果有任何变更无法干净地被应用。

对文件的变更被重新应用,但是被暂存的文件没有重新被暂存。想那样的话,你必须在运行 git stash apply 命令时带上一个 --index 的选项来告诉命令重新应用被暂存的变更。如果你是这么做的,你应该已经回到你原来的位置:

$ git stash apply --index# On branch master# Changes to be committed:#   (use "git reset HEAD <file>..." to unstage)##      modified:   index.html## Changed but not updated:#   (use "git add <file>..." to update what will be committed)##      modified:   lib/simplegit.rb#apply 选项只尝试应用储藏的工作——储藏的内容仍然在栈上。要移除它,你可以运行 git stash drop,加上你希望移除的储藏的名字:

$ git stash liststash@{0}: WIP on master: 049d078 added the index filestash@{1}: WIP on master: c264051... Revert "added file_size"stash@{2}: WIP on master: 21d80a5... added number to log$ git stash drop stash@{0}Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)你也可以运行 git stash pop 来重新应用储藏,同时立刻将其从堆栈中移走。

Un-applying a Stash
In some use case scenarios you might want to apply stashed changes, do some work, but then un-apply those changes that originally came form the stash. Git does not provide such astash unapply command, but it is possible to achieve the effect by simply retrieving the patch associated with a stash and applying it in reverse:

$ git stash show -p stash@{0} | git apply -RAgain, if you don’t specify a stash, Git assumes the most recent stash:

$ git stash show -p | git apply -RYou may want to create an alias and effectively add a stash-unapply command to your git. For example:

$ git config --global alias.stash-unapply '!git stash show -p | git apply -R'$ git stash$ #... work work work$ git stash-unapply从储藏中创建分支
如果你储藏了一些工作,暂时不去理会,然后继续在你储藏工作的分支上工作,你在重新应用工作时可能会碰到一些问题。如果尝试应用的变更是针对一个你那之后修改过的文件,你会碰到一个归并冲突并且必须去化解它。如果你想用更方便的方法来重新检验你储藏的变更,你可以运行git stash branch,这会创建一个新的分支,检出你储藏工作时的所处的提交,重新应用你的工作,如果成功,将会丢弃储藏。

$ git stash branch testchangesSwitched to a new branch "testchanges"# On branch testchanges# Changes to be committed:#   (use "git reset HEAD <file>..." to unstage)##      modified:   index.html## Changed but not updated:#   (use "git add <file>..." to update what will be committed)##      modified:   lib/simplegit.rb#Dropped refs/stash@{0} (f0dfc4d5dc332d1cee34a634182e168c4efc3359)这是一个很棒的捷径来恢复储藏的工作然后在新的分支上继续当时的工作。



6.4  重写历史
很多时候,在 Git 上工作的时候,你也许会由于某种原因想要修订你的提交历史。Git 的一个卓越之处就是它允许你在最后可能的时刻再作决定。你可以在你即将提交暂存区时决定什么文件归入哪一次提交,你可以使用 stash 命令来决定你暂时搁置的工作,你可以重写已经发生的提交以使它们看起来是另外一种样子。这个包括改变提交的次序、改变说明或者修改提交中包含的文件,将提交归并、拆分或者完全删除——这一切在你尚未开始将你的工作和别人共享前都是可以的。

在这一节中,你会学到如何完成这些很有用的任务以使你的提交历史在你将其共享给别人之前变成你想要的样子。

改变最近一次提交
改变最近一次提交也许是最常见的重写历史的行为。对于你的最近一次提交,你经常想做两件基本事情:改变提交说明,或者改变你刚刚通过增加,改变,删除而记录的快照。

如果你只想修改最近一次提交说明,这非常简单:

$ git commit --amend这会把你带入文本编辑器,里面包含了你最近一次提交说明,供你修改。当你保存并退出编辑器,这个编辑器会写入一个新的提交,里面包含了那个说明,并且让它成为你的新的最近一次提交。

如果你完成提交后又想修改被提交的快照,增加或者修改其中的文件,可能因为你最初提交时,忘了添加一个新建的文件,这个过程基本上一样。你通过修改文件然后对其运行git add或对一个已被记录的文件运行git rm,随后的git commit --amend会获取你当前的暂存区并将它作为新提交对应的快照。

使用这项技术的时候你必须小心,因为修正会改变提交的SHA-1值。这个很像是一次非常小的rebase——不要在你最近一次提交被推送后还去修正它。

修改多个提交说明
要修改历史中更早的提交,你必须采用更复杂的工具。Git没有一个修改历史的工具,但是你可以使用rebase工具来衍合一系列的提交到它们原来所在的HEAD上而不是移到新的上。依靠这个交互式的rebase工具,你就可以停留在每一次提交后,如果你想修改或改变说明、增加文件或任何其他事情。你可以通过给git rebase增加-i选项来以交互方式地运行rebase。你必须通过告诉命令衍合到哪次提交,来指明你需要重写的提交的回溯深度。

例如,你想修改最近三次的提交说明,或者其中任意一次,你必须给git rebase -i提供一个参数,指明你想要修改的提交的父提交,例如HEAD~2或者HEAD~3。可能记住~3更加容易,因为你想修改最近三次提交;但是请记住你事实上所指的是四次提交之前,即你想修改的提交的父提交。

$ git rebase -i HEAD~3再次提醒这是一个衍合命令——HEAD~3..HEAD范围内的每一次提交都会被重写,无论你是否修改说明。不要涵盖你已经推送到中心服务器的提交——这么做会使其他开发者产生混乱,因为你提供了同样变更的不同版本。

运行这个命令会为你的文本编辑器提供一个提交列表,看起来像下面这样

pick f7f3f6d changed my name a bitpick 310154e updated README formatting and added blamepick a5f4a0d added cat-file# Rebase 710f0f8..a5f4a0d onto 710f0f8## Commands:#  p, pick = use commit#  e, edit = use commit, but stop for amending#  s, squash = use commit, but meld into previous commit## If you remove a line here THAT COMMIT WILL BE LOST.# However, if you remove everything, the rebase will be aborted.#很重要的一点是你得注意这些提交的顺序与你通常通过log命令看到的是相反的。如果你运行log,你会看到下面这样的结果:

$ git log --pretty=format:"%h %s" HEAD~3..HEADa5f4a0d added cat-file310154e updated README formatting and added blamef7f3f6d changed my name a bit请注意这里的倒序。交互式的rebase给了你一个即将运行的脚本。它会从你在命令行上指明的提交开始(HEAD~3)然后自上至下重播每次提交里引入的变更。它将最早的列在顶上而不是最近的,因为这是第一个需要重播的。

你需要修改这个脚本来让它停留在你想修改的变更上。要做到这一点,你只要将你想修改的每一次提交前面的pick改为edit。例如,只想修改第三次提交说明的话,你就像下面这样修改文件:

edit f7f3f6d changed my name a bitpick 310154e updated README formatting and added blamepick a5f4a0d added cat-file当你保存并退出编辑器,Git会倒回至列表中的最后一次提交,然后把你送到命令行中,同时显示以下信息:

$ git rebase -i HEAD~3Stopped at 7482e0d... updated the gemspec to hopefully work betterYou can amend the commit now, with       git commit --amendOnce you’re satisfied with your changes, run       git rebase --continue这些指示很明确地告诉了你该干什么。输入

$ git commit --amend修改提交说明,退出编辑器。然后,运行

$ git rebase --continue这个命令会自动应用其他两次提交,你就完成任务了。如果你将更多行的 pick 改为 edit ,你就能对你想修改的提交重复这些步骤。Git每次都会停下,让你修正提交,完成后继续运行。

重排提交
你也可以使用交互式的衍合来彻底重排或删除提交。如果你想删除”added cat-file”这个提交并且修改其他两次提交引入的顺序,你将rebase脚本从这个

pick f7f3f6d changed my name a bitpick 310154e updated README formatting and added blamepick a5f4a0d added cat-file改为这个:

pick 310154e updated README formatting and added blamepick f7f3f6d changed my name a bit当你保存并退出编辑器,Git 将分支倒回至这些提交的父提交,应用310154e,然后f7f3f6d,接着停止。你有效地修改了这些提交的顺序并且彻底删除了”added cat-file”这次提交。

压制(Squashing)提交
交互式的衍合工具还可以将一系列提交压制为单一提交。脚本在 rebase 的信息里放了一些有用的指示:

## Commands:#  p, pick = use commit#  e, edit = use commit, but stop for amending#  s, squash = use commit, but meld into previous commit## If you remove a line here THAT COMMIT WILL BE LOST.# However, if you remove everything, the rebase will be aborted.#如果不用”pick”或者”edit”,而是指定”squash”,Git 会同时应用那个变更和它之前的变更并将提交说明归并。因此,如果你想将这三个提交合并为单一提交,你可以将脚本修改成这样:

pick f7f3f6d changed my name a bitsquash 310154e updated README formatting and added blamesquash a5f4a0d added cat-file当你保存并退出编辑器,Git 会应用全部三次变更然后将你送回编辑器来归并三次提交说明。

# This is a combination of 3 commits.# The first commit's message is:changed my name a bit# This is the 2nd commit message:updated README formatting and added blame# This is the 3rd commit message:added cat-file当你保存之后,你就拥有了一个包含前三次提交的全部变更的单一提交。

拆分提交
拆分提交就是撤销一次提交,然后多次部分地暂存或提交直到结束。例如,假设你想将三次提交中的中间一次拆分。将”updated README formatting and added blame”拆分成两次提交:第一次为”updated README formatting”,第二次为”added blame”。你可以在rebase -i脚本中修改你想拆分的提交前的指令为”edit”:

pick f7f3f6d changed my name a bitedit 310154e updated README formatting and added blamepick a5f4a0d added cat-file然后,这个脚本就将你带入命令行,你重置那次提交,提取被重置的变更,从中创建多次提交。当你保存并退出编辑器,Git 倒回到列表中第一次提交的父提交,应用第一次提交(f7f3f6d),应用第二次提交(310154e),然后将你带到控制台。那里你可以用git reset HEAD^对那次提交进行一次混合的重置,这将撤销那次提交并且将修改的文件撤回。此时你可以暂存并提交文件,直到你拥有多次提交,结束后,运行git rebase --continue。

$ git reset HEAD^$ git add README$ git commit -m 'updated README formatting'$ git add lib/simplegit.rb$ git commit -m 'added blame'$ git rebase --continueGit在脚本中应用了最后一次提交(a5f4a0d),你的历史看起来就像这样了:

$ git log -4 --pretty=format:"%h %s"1c002dd added cat-file9b29157 added blame35cfb2b updated README formattingf3cc40e changed my name a bit再次提醒,这会修改你列表中的提交的 SHA 值,所以请确保这个列表里不包含你已经推送到共享仓库的提交。

核弹级选项: filter-branch
如果你想用脚本的方式修改大量的提交,还有一个重写历史的选项可以用——例如,全局性地修改电子邮件地址或者将一个文件从所有提交中删除。这个命令是filter-branch,这个会大面积地修改你的历史,所以你很有可能不该去用它,除非你的项目尚未公开,没有其他人在你准备修改的提交的基础上工作。尽管如此,这个可以非常有用。你会学习一些常见用法,借此对它的能力有所认识。

从所有提交中删除一个文件
这个经常发生。有些人不经思考使用git add .,意外地提交了一个巨大的二进制文件,你想将它从所有地方删除。也许你不小心提交了一个包含密码的文件,而你想让你的项目开源。filter-branch大概会是你用来清理整个历史的工具。要从整个历史中删除一个名叫password.txt的文件,你可以在filter-branch上使用--tree-filter选项:

$ git filter-branch --tree-filter 'rm -f passwords.txt' HEADRewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21)Ref 'refs/heads/master' was rewritten--tree-filter选项会在每次检出项目时先执行指定的命令然后重新提交结果。在这个例子中,你会在所有快照中删除一个名叫 password.txt 的文件,无论它是否存在。如果你想删除所有不小心提交上去的编辑器备份文件,你可以运行类似git filter-branch --tree-filter 'rm -f *~' HEAD的命令。

你可以观察到 Git 重写目录树并且提交,然后将分支指针移到末尾。一个比较好的办法是在一个测试分支上做这些然后在你确定产物真的是你所要的之后,再 hard-reset 你的主分支。要在你所有的分支上运行filter-branch的话,你可以传递一个--all给命令。

将一个子目录设置为新的根目录
假设你完成了从另外一个代码控制系统的导入工作,得到了一些没有意义的子目录(trunk, tags等等)。如果你想让trunk子目录成为每一次提交的新的项目根目录,filter-branch也可以帮你做到:

$ git filter-branch --subdirectory-filter trunk HEADRewrite 856f0bf61e41a27326cdae8f09fe708d679f596f (12/12)Ref 'refs/heads/master' was rewritten现在你的项目根目录就是trunk子目录了。Git 会自动地删除不对这个子目录产生影响的提交。

全局性地更换电子邮件地址
另一个常见的案例是你在开始时忘了运行git config来设置你的姓名和电子邮件地址,也许你想开源一个项目,把你所有的工作电子邮件地址修改为个人地址。无论哪种情况你都可以用filter-branch来更换多次提交里的电子邮件地址。你必须小心一些,只改变属于你的电子邮件地址,所以你使用--commit-filter:

$ git filter-branch --commit-filter '        if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ];        then                GIT_AUTHOR_NAME="Scott Chacon";                GIT_AUTHOR_EMAIL="schacon@example.com";                git commit-tree "$@";        else                git commit-tree "$@";        fi' HEAD这个会遍历并重写所有提交使之拥有你的新地址。因为提交里包含了它们的父提交的SHA-1值,这个命令会修改你的历史中的所有提交,而不仅仅是包含了匹配的电子邮件地址的那些。



6.5  使用 Git 调试
Git 同样提供了一些工具来帮助你调试项目中遇到的问题。由于 Git 被设计为可应用于几乎任何类型的项目,这些工具是通用型,但是在遇到问题时可以经常帮助你查找缺陷所在。

文件标注
如果你在追踪代码中的缺陷想知道这是什么时候为什么被引进来的,文件标注会是你的最佳工具。它会显示文件中对每一行进行修改的最近一次提交。因此,如果你发现自己代码中的一个方法存在缺陷,你可以用git blame来标注文件,查看那个方法的每一行分别是由谁在哪一天修改的。下面这个例子使用了-L选项来限制输出范围在第12至22行:

$ git blame -L 12,22 simplegit.rb ^4832fe2 (Scott Chacon  2008-03-15 10:31:28 -0700 12)  def show(tree = 'master')^4832fe2 (Scott Chacon  2008-03-15 10:31:28 -0700 13)   command("git show #{tree}"^4832fe2 (Scott Chacon  2008-03-15 10:31:28 -0700 14)  end^4832fe2 (Scott Chacon  2008-03-15 10:31:28 -0700 15)9f6560e4 (Scott Chacon  2008-03-17 21:52:20 -0700 16)  def log(tree = 'master')79eaf55d (Scott Chacon  2008-04-06 10:15:08 -0700 17)   command("git log #{tree}"9f6560e4 (Scott Chacon  2008-03-17 21:52:20 -0700 1  end9f6560e4 (Scott Chacon  2008-03-17 21:52:20 -0700 19) 42cf2861 (Magnus Chacon 2008-04-13 10:45:01 -0700 20)  def blame(path)42cf2861 (Magnus Chacon 2008-04-13 10:45:01 -0700 21)   command("git blame #{path}"42cf2861 (Magnus Chacon 2008-04-13 10:45:01 -0700 22)  end请注意第一个域里是最后一次修改该行的那次提交的 SHA-1 值。接下去的两个域是从那次提交中抽取的值——作者姓名和日期——所以你可以方便地获知谁在什么时候修改了这一行。在这后面是行号和文件的内容。请注意^4832fe2提交的那些行,这些指的是文件最初提交的那些行。那个提交是文件第一次被加入这个项目时存在的,自那以后未被修改过。这会带来小小的困惑,因为你已经至少看到了Git使用^来修饰一个提交的SHA值的三种不同的意义,但这里确实就是这个意思。

另一件很酷的事情是在 Git 中你不需要显式地记录文件的重命名。它会记录快照然后根据现实尝试找出隐式的重命名动作。这其中有一个很有意思的特性就是你可以让它找出所有的代码移动。如果你在git blame后加上-C,Git会分析你在标注的文件然后尝试找出其中代码片段的原始出处,如果它是从其他地方拷贝过来的话。最近,我在将一个名叫GITServerHandler.m的文件分解到多个文件中,其中一个是GITPackUpload.m。通过对GITPackUpload.m执行带-C参数的blame命令,我可以看到代码块的原始出处:

$ git blame -C -L 141,153 GITPackUpload.m f344f58d GITServerHandler.m (Scott 2009-01-04 141) f344f58d GITServerHandler.m (Scott 2009-01-04 142) - (void) gatherObjectShasFromCf344f58d GITServerHandler.m (Scott 2009-01-04 143) {70befddd GITServerHandler.m (Scott 2009-03-22 144)         //NSLog(@"GATHER COMMIad11ac80 GITPackUpload.m    (Scott 2009-03-24 145)ad11ac80 GITPackUpload.m    (Scott 2009-03-24 146)         NSString *parentSha;ad11ac80 GITPackUpload.m    (Scott 2009-03-24 147)         GITCommit *commit = [gad11ac80 GITPackUpload.m    (Scott 2009-03-24 14ad11ac80 GITPackUpload.m    (Scott 2009-03-24 149)         //NSLog(@"GATHER COMMIad11ac80 GITPackUpload.m    (Scott 2009-03-24 150)56ef2caf GITServerHandler.m (Scott 2009-01-05 151)         if(commit) {56ef2caf GITServerHandler.m (Scott 2009-01-05 152)                 [refDict setOb56ef2caf GITServerHandler.m (Scott 2009-01-05 153)这真的非常有用。通常,你会把你拷贝代码的那次提交作为原始提交,因为这是你在这个文件中第一次接触到那几行。Git可以告诉你编写那些行的原始提交,即便是在另一个文件里。

二分查找
标注文件在你知道问题是哪里引入的时候会有帮助。如果你不知道,并且自上次代码可用的状态已经经历了上百次的提交,你可能就要求助于bisect命令了。bisect会在你的提交历史中进行二分查找来尽快地确定哪一次提交引入了错误。

例如你刚刚推送了一个代码发布版本到产品环境中,对代码为什么会表现成那样百思不得其解。你回到你的代码中,还好你可以重现那个问题,但是找不到在哪里。你可以对代码执行bisect来寻找。首先你运行git bisect start启动,然后你用git bisect bad来告诉系统当前的提交已经有问题了。然后你必须告诉bisect已知的最后一次正常状态是哪次提交,使用git bisect good [good_commit]:

$ git bisect start$ git bisect bad$ git bisect good v1.0Bisecting: 6 revisions left to test after this[ecb6e1bc347ccecc5f9350d878ce677feb13d3b2] error handling on repoGit 发现在你标记为正常的提交(v1.0)和当前的错误版本之间有大约12次提交,于是它检出中间的一个。在这里,你可以运行测试来检查问题是否存在于这次提交。如果是,那么它是在这个中间提交之前的某一次引入的;如果否,那么问题是在中间提交之后引入的。假设这里是没有错误的,那么你就通过git bisect good来告诉 Git 然后继续你的旅程:

$ git bisect goodBisecting: 3 revisions left to test after this[b047b02ea83310a70fd603dc8cd7a6cd13d15c04] secure this thing现在你在另外一个提交上了,在你刚刚测试通过的和一个错误提交的中点处。你再次运行测试然后发现这次提交是错误的,因此你通过git bisect bad来告诉Git:

$ git bisect badBisecting: 1 revisions left to test after this[f71ce38690acf49c1f3c9bea38e09d82a5ce6014] drop exceptions table这次提交是好的,那么 Git 就获得了确定问题引入位置所需的所有信息。它告诉你第一个错误提交的 SHA-1 值并且显示一些提交说明以及哪些文件在那次提交里修改过,这样你可以找出缺陷被引入的根源:

$ git bisect goodb047b02ea83310a70fd603dc8cd7a6cd13d15c04 is first bad commitcommit b047b02ea83310a70fd603dc8cd7a6cd13d15c04Author: PJ Hyett <pjhyett@example.com>Date:   Tue Jan 27 14:48:32 2009 -0800    secure this thing:040000 040000 40ee3e7821b895e52c1695092db9bdc4c61d1730f24d3c6ebcfc639b1a3814550e62d60b8e68a8e4 M  config当你完成之后,你应该运行git bisect reset来重设你的HEAD到你开始前的地方,否则你会处于一个诡异的地方:

$ git bisect reset这是个强大的工具,可以帮助你检查上百的提交,在几分钟内找出缺陷引入的位置。事实上,如果你有一个脚本会在工程正常时返回0,错误时返回非0的话,你可以完全自动地执行git bisect。首先你需要提供已知的错误和正确提交来告诉它二分查找的范围。你可以通过bisect start命令来列出它们,先列出已知的错误提交再列出已知的正确提交:

$ git bisect start HEAD v1.0$ git bisect run test-error.sh这样会自动地在每一个检出的提交里运行test-error.sh直到Git找出第一个破损的提交。你也可以运行像make或者make tests或者任何你所拥有的来为你执行自动化的测试。



6.6  子模块
经常有这样的事情,当你在一个项目上工作时,你需要在其中使用另外一个项目。也许它是一个第三方开发的库或者是你独立开发和并在多个父项目中使用的。这个场景下一个常见的问题产生了:你想将两个项目单独处理但是又需要在其中一个中使用另外一个。

这里有一个例子。假设你在开发一个网站,为之创建Atom源。你不想编写一个自己的Atom生成代码,而是决定使用一个库。你可能不得不像CPAN install或者Ruby gem一样包含来自共享库的代码,或者将代码拷贝到你的项目树中。如果采用包含库的办法,那么不管用什么办法都很难去定制这个库,部署它就更加困难了,因为你必须确保每个客户都拥有那个库。把代码包含到你自己的项目中带来的问题是,当上游被修改时,任何你进行的定制化的修改都很难归并。

Git 通过子模块处理这个问题。子模块允许你将一个 Git 仓库当作另外一个Git仓库的子目录。这允许你克隆另外一个仓库到你的项目中并且保持你的提交相对独立。

子模块初步
假设你想把 Rack 库(一个 Ruby 的 web 服务器网关接口)加入到你的项目中,可能既要保持你自己的变更,又要延续上游的变更。首先你要把外部的仓库克隆到你的子目录中。你通过git submodule add将外部项目加为子模块:

$ git submodule add git://github.com/chneukirchen/rack.git rackInitialized empty Git repository in /opt/subtest/rack/.git/remote: Counting objects: 3181, done.remote: Compressing objects: 100% (1534/1534), done.remote: Total 3181 (delta 1951), reused 2623 (delta 1603)Receiving objects: 100% (3181/3181), 675.42 KiB | 422 KiB/s, done.Resolving deltas: 100% (1951/1951), done.现在你就在项目里的rack子目录下有了一个 Rack 项目。你可以进入那个子目录,进行变更,加入你自己的远程可写仓库来推送你的变更,从原始仓库拉取和归并等等。如果你在加入子模块后立刻运行git status,你会看到下面两项:

$ git status# On branch master# Changes to be committed:#   (use "git reset HEAD <file>..." to unstage)##      new file:   .gitmodules#      new file:   rack#首先你注意到有一个.gitmodules文件。这是一个配置文件,保存了项目 URL 和你拉取到的本地子目录

$ cat .gitmodules [submodule "rack"]      path = rack      url = git://github.com/chneukirchen/rack.git如果你有多个子模块,这个文件里会有多个条目。很重要的一点是这个文件跟其他文件一样也是处于版本控制之下的,就像你的.gitignore文件一样。它跟项目里的其他文件一样可以被推送和拉取。这是其他克隆此项目的人获知子模块项目来源的途径。

git status的输出里所列的另一项目是 rack 。如果你运行在那上面运行git diff,会发现一些有趣的东西:

$ git diff --cached rackdiff --git a/rack b/racknew file mode 160000index 0000000..08d709f--- /dev/null+++ b/rack@@ -0,0 +1 @@+Subproject commit 08d709f78b8c5b0fbeb7821e37fa53e69afcf433尽管rack是你工作目录里的子目录,但 Git 把它视作一个子模块,当你不在那个目录里时并不记录它的内容。取而代之的是,Git 将它记录成来自那个仓库的一个特殊的提交。当你在那个子目录里修改并提交时,子项目会通知那里的 HEAD 已经发生变更并记录你当前正在工作的那个提交;通过那样的方法,当其他人克隆此项目,他们可以重新创建一致的环境。

这是关于子模块的重要一点:你记录他们当前确切所处的提交。你不能记录一个子模块的master或者其他的符号引用。

当你提交时,会看到类似下面的:

$ git commit -m 'first commit with submodule rack'[master 0550271] first commit with submodule rack 2 files changed, 4 insertions(+), 0 deletions(-) create mode 100644 .gitmodules create mode 160000 rack注意 rack 条目的 160000 模式。这在Git中是一个特殊模式,基本意思是你将一个提交记录为一个目录项而不是子目录或者文件。

你可以将rack目录当作一个独立的项目,保持一个指向子目录的最新提交的指针然后反复地更新上层项目。所有的Git命令都在两个子目录里独立工作:

$ git log -1commit 0550271328a0038865aad6331e620cd7238601bbAuthor: Scott Chacon <schacon@gmail.com>Date:   Thu Apr 9 09:03:56 2009 -0700    first commit with submodule rack$ cd rack/$ git log -1commit 08d709f78b8c5b0fbeb7821e37fa53e69afcf433Author: Christian Neukirchen <chneukirchen@gmail.com>Date:   Wed Mar 25 14:49:04 2009 +0100    Document version change克隆一个带子模块的项目
这里你将克隆一个带子模块的项目。当你接收到这样一个项目,你将得到了包含子项目的目录,但里面没有文件:

$ git clone git://github.com/schacon/myproject.gitInitialized empty Git repository in /opt/myproject/.git/remote: Counting objects: 6, done.remote: Compressing objects: 100% (4/4), done.remote: Total 6 (delta 0), reused 0 (delta 0)Receiving objects: 100% (6/6), done.$ cd myproject$ ls -ltotal 8-rw-r--r--  1 schacon  admin   3 Apr  9 09:11 READMEdrwxr-xr-x  2 schacon  admin  68 Apr  9 09:11 rack$ ls rack/$rack目录存在了,但是是空的。你必须运行两个命令:git submodule init来初始化你的本地配置文件,git submodule update来从那个项目拉取所有数据并检出你上层项目里所列的合适的提交:

$ git submodule initSubmodule 'rack' (git://github.com/chneukirchen/rack.git) registered for path 'rack'$ git submodule updateInitialized empty Git repository in /opt/myproject/rack/.git/remote: Counting objects: 3181, done.remote: Compressing objects: 100% (1534/1534), done.remote: Total 3181 (delta 1951), reused 2623 (delta 1603)Receiving objects: 100% (3181/3181), 675.42 KiB | 173 KiB/s, done.Resolving deltas: 100% (1951/1951), done.Submodule path 'rack': checked out '08d709f78b8c5b0fbeb7821e37fa53e69afcf433'现在你的rack子目录就处于你先前提交的确切状态了。如果另外一个开发者变更了 rack 的代码并提交,你拉取那个引用然后归并之,将得到稍有点怪异的东西:

$ git merge origin/masterUpdating 0550271..85a3eeeFast forward rack |    2 +- 1 files changed, 1 insertions(+), 1 deletions(-)[master*]$ git status# On branch master# Changed but not updated:#   (use "git add <file>..." to update what will be committed)#   (use "git checkout -- <file>..." to discard changes in working directory)##      modified:   rack#你归并来的仅仅上是一个指向你的子模块的指针;但是它并不更新你子模块目录里的代码,所以看起来你的工作目录处于一个临时状态:

$ git diffdiff --git a/rack b/rackindex 6c5e70b..08d709f 160000--- a/rack+++ b/rack@@ -1 +1 @@-Subproject commit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0+Subproject commit 08d709f78b8c5b0fbeb7821e37fa53e69afcf433事情就是这样,因为你所拥有的子模块的指针并对应于子模块目录的真实状态。为了修复这一点,你必须再次运行git submodule update:

$ git submodule updateremote: Counting objects: 5, done.remote: Compressing objects: 100% (3/3), done.remote: Total 3 (delta 1), reused 2 (delta 0)Unpacking objects: 100% (3/3), done.From git@github.com:schacon/rack   08d709f..6c5e70b  master     -> origin/masterSubmodule path 'rack': checked out '6c5e70b984a60b3cecd395edd5b48a7575bf58e0'每次你从主项目中拉取一个子模块的变更都必须这样做。看起来很怪但是管用。

一个常见问题是当开发者对子模块做了一个本地的变更但是并没有推送到公共服务器。然后他们提交了一个指向那个非公开状态的指针然后推送上层项目。当其他开发者试图运行git submodule update,那个子模块系统会找不到所引用的提交,因为它只存在于第一个开发者的系统中。如果发生那种情况,你会看到类似这样的错误:

$ git submodule updatefatal: reference isn’t a tree: 6c5e70b984a60b3cecd395edd5b48a7575bf58e0Unable to checkout '6c5e70b984a60b3cecd395edd5ba7575bf58e0' in submodule path 'rack'你不得不去查看谁最后变更了子模块

$ git log -1 rackcommit 85a3eee996800fcfa91e2119372dd4172bf76678Author: Scott Chacon <schacon@gmail.com>Date:   Thu Apr 9 09:19:14 2009 -0700    added a submodule reference I will never make public. hahahahaha!然后,你给那个家伙发电子邮件说他一通。

上层项目
有时候,开发者想按照他们的分组获取一个大项目的子目录的子集。如果你是从 CVS 或者 Subversion 迁移过来的话这个很常见,在那些系统中你已经定义了一个模块或者子目录的集合,而你想延续这种类型的工作流程。

在 Git 中实现这个的一个好办法是你将每一个子目录都做成独立的 Git 仓库,然后创建一个上层项目的 Git 仓库包含多个子模块。这个办法的一个优势是你可以在上层项目中通过标签和分支更为明确地定义项目之间的关系。

子模块的问题
使用子模块并非没有任何缺点。首先,你在子模块目录中工作时必须相对小心。当你运行git submodule update,它会检出项目的指定版本,但是不在分支内。这叫做获得一个分离的头——这意味着 HEAD 文件直接指向一次提交,而不是一个符号引用。问题在于你通常并不想在一个分离的头的环境下工作,因为太容易丢失变更了。如果你先执行了一次submodule update,然后在那个子模块目录里不创建分支就进行提交,然后再次从上层项目里运行git submodule update同时不进行提交,Git会毫无提示地覆盖你的变更。技术上讲你不会丢失工作,但是你将失去指向它的分支,因此会很难取到。

为了避免这个问题,当你在子模块目录里工作时应使用git checkout -b work创建一个分支。当你再次在子模块里更新的时候,它仍然会覆盖你的工作,但是至少你拥有一个可以回溯的指针。

切换带有子模块的分支同样也很有技巧。如果你创建一个新的分支,增加了一个子模块,然后切换回不带该子模块的分支,你仍然会拥有一个未被追踪的子模块的目录

$ git checkout -b rackSwitched to a new branch "rack"$ git submodule add git@github.com:schacon/rack.git rackInitialized empty Git repository in /opt/myproj/rack/.git/...Receiving objects: 100% (3184/3184), 677.42 KiB | 34 KiB/s, done.Resolving deltas: 100% (1952/1952), done.$ git commit -am 'added rack submodule'[rack cc49a69] added rack submodule 2 files changed, 4 insertions(+), 0 deletions(-) create mode 100644 .gitmodules create mode 160000 rack$ git checkout masterSwitched to branch "master"$ git status# On branch master# Untracked files:#   (use "git add <file>..." to include in what will be committed)##      rack/你将不得不将它移走或者删除,这样的话当你切换回去的时候必须重新克隆它——你可能会丢失你未推送的本地的变更或分支。

最后一个需要引起注意的是关于从子目录切换到子模块的。如果你已经跟踪了你项目中的一些文件但是想把它们移到子模块去,你必须非常小心,否则Git会生你的气。假设你的项目中有一个子目录里放了 rack 的文件,然后你想将它转换为子模块。如果你删除子目录然后运行submodule add,Git会向你大吼:

$ rm -Rf rack/$ git submodule add git@github.com:schacon/rack.git rack'rack' already exists in the index你必须先将rack目录撤回。然后你才能加入子模块:

$ git rm -r rack$ git submodule add git@github.com:schacon/rack.git rackInitialized empty Git repository in /opt/testsub/rack/.git/remote: Counting objects: 3184, done.remote: Compressing objects: 100% (1465/1465), done.remote: Total 3184 (delta 1952), reused 2770 (delta 1675)Receiving objects: 100% (3184/3184), 677.42 KiB | 88 KiB/s, done.Resolving deltas: 100% (1952/1952), done.现在假设你在一个分支里那样做了。如果你尝试切换回一个仍然在目录里保留那些文件而不是子模块的分支时——你会得到下面的错误:

$ git checkout mastererror: Untracked working tree file 'rack/AUTHORS' would be overwritten by merge.你必须先移除rack子模块的目录才能切换到不包含它的分支:

$ mv rack /tmp/$ git checkout masterSwitched to branch "master"$ lsREADME        rack然后,当你切换回来,你会得到一个空的rack目录。你可以运行git submodule update重新克隆,也可以将/tmp/rack目录重新移回空目录。



6.7  子树合并
现在你已经看到了子模块系统的麻烦之处,让我们来看一下解决相同问题的另一途径。当 Git 归并时,它会检查需要归并的内容然后选择一个合适的归并策略。如果你归并的分支是两个,Git使用一个_递归_策略。如果你归并的分支超过两个,Git采用_章鱼_策略。这些策略是自动选择的,因为递归策略可以处理复杂的三路归并情况——比如多于一个共同祖先的——但是它只能处理两个分支的归并。章鱼归并可以处理多个分支但是但必须更加小心以避免冲突带来的麻烦,因此它被选中作为归并两个以上分支的默认策略。

实际上,你也可以选择其他策略。其中的一个就是_子树_归并,你可以用它来处理子项目问题。这里你会看到如何换用子树归并的方法来实现前一节里所做的 rack 的嵌入。

子树归并的思想是你拥有两个工程,其中一个项目映射到另外一个项目的子目录中,反过来也一样。当你指定一个子树归并,Git可以聪明地探知其中一个是另外一个的子树从而实现正确的归并——这相当神奇。

首先你将 Rack 应用加入到项目中。你将 Rack 项目当作你项目中的一个远程引用,然后将它检出到它自身的分支:

$ git remote add rack_remote git@github.com:schacon/rack.git$ git fetch rack_remotewarning: no common commitsremote: Counting objects: 3184, done.remote: Compressing objects: 100% (1465/1465), done.remote: Total 3184 (delta 1952), reused 2770 (delta 1675)Receiving objects: 100% (3184/3184), 677.42 KiB | 4 KiB/s, done.Resolving deltas: 100% (1952/1952), done.From git@github.com:schacon/rack * [new branch]      build      -> rack_remote/build * [new branch]      master     -> rack_remote/master * [new branch]      rack-0.4   -> rack_remote/rack-0.4 * [new branch]      rack-0.9   -> rack_remote/rack-0.9$ git checkout -b rack_branch rack_remote/masterBranch rack_branch set up to track remote branch refs/remotes/rack_remote/master.Switched to a new branch "rack_branch"现在在你的rack_branch分支中就有了Rack项目的根目录,而你自己的项目在master分支中。如果你先检出其中一个然后另外一个,你会看到它们有不同的项目根目录:

$ lsAUTHORS               KNOWN-ISSUES   Rakefile      contrib               libCOPYING               README         bin           example               test$ git checkout masterSwitched to branch "master"$ lsREADME要将 Rack 项目当作子目录拉取到你的master项目中。你可以在 Git 中用git read-tree来实现。你会在第9章学到更多与read-tree和它的朋友相关的东西,当前你会知道它读取一个分支的根目录树到当前的暂存区和工作目录。你只要切换回你的master分支,然后拉取rack分支到你主项目的master分支的rack子目录:

$ git read-tree --prefix=rack/ -u rack_branch当你提交的时候,看起来就像你在那个子目录下拥有Rack的文件——就像你从一个tarball里拷贝的一样。有意思的是你可以比较容易地归并其中一个分支的变更到另外一个。因此,如果 Rack 项目更新了,你可以通过切换到那个分支并执行拉取来获得上游的变更:

$ git checkout rack_branch$ git pull然后,你可以将那些变更归并回你的 master 分支。你可以使用git merge -s subtree,它会工作的很好;但是 Git 同时会把历史归并到一起,这可能不是你想要的。为了拉取变更并预置提交说明,需要在-s subtree策略选项的同时使用--squash和--no-commit选项。

$ git checkout master$ git merge --squash -s subtree --no-commit rack_branchSquash commit -- not updating HEADAutomatic merge went well; stopped before committing as requested所有 Rack 项目的变更都被归并可以进行本地提交。你也可以做相反的事情——在你主分支的rack目录里进行变更然后归并回rack_branch分支,然后将它们提交给维护者或者推送到上游。

为了得到rack子目录和你rack_branch分支的区别——以决定你是否需要归并它们——你不能使用一般的diff命令。而是对你想比较的分支运行git diff-tree:

$ git diff-tree -p rack_branch或者,为了比较你的rack子目录和服务器上你拉取时的master分支,你可以运行

$ git diff-tree -p rack_remote/master

6.8  总结
你已经看到了很多高级的工具,允许你更加精确地操控你的提交和暂存区。当你碰到问题时,你应该可以很容易找出是哪个分支什么时候由谁引入了它们。如果你想在项目中使用子项目,你也已经学会了一些方法来满足这些需求。到此,你应该能够完成日常里你需要用命令行在 Git 下做的大部分事情,并且感到比较顺手

作者: compare2000    时间: 2014-05-22 17:39
Git详解七:自定义Git  
自定义 Git
到目前为止,我阐述了 Git 基本的运作机制和使用方式,介绍了 Git 提供的许多工具来帮助你简单且有效地使用它。 在本章,我将会介绍 Git 的一些重要的配置方法和钩子机制以满足自定义的要求。通过这些工具,它会和你和公司或团队配合得天衣无缝。



7.1  配置 Git
如第一章所言,用git config配置 Git,要做的第一件事就是设置名字和邮箱地址:

$ git config --global user.name "John Doe"$ git config --global user.email johndoe@example.com从现在开始,你会了解到一些类似以上但更为有趣的设置选项来自定义 Git。

先过一遍第一章中提到的 Git 配置细节。Git 使用一系列的配置文件来存储你定义的偏好,它首先会查找/etc/gitconfig文件,该文件含有 对系统上所有用户及他们所拥有的仓库都生效的配置值(译注:gitconfig是全局配置文件), 如果传递--system选项给git config命令, Git 会读写这个文件。

接下来 Git 会查找每个用户的~/.gitconfig文件,你能传递--global选项让 Git读写该文件。

最后 Git 会查找由用户定义的各个库中 Git 目录下的配置文件(.git/config),该文件中的值只对属主库有效。 以上阐述的三层配置从一般到特殊层层推进,如果定义的值有冲突,以后面层中定义的为准,例如:在.git/config和/etc/gitconfig的较量中,.git/config取得了胜利。虽然你也可以直接手动编辑这些配置文件,但是运行git config命令将会来得简单些。

客户端基本配置
Git 能够识别的配置项被分为了两大类:客户端和服务器端,其中大部分基于你个人工作偏好,属于客户端配置。尽管有数不尽的选项,但我只阐述 其中经常使用或者会对你的工作流产生巨大影响的选项,如果你想观察你当前的 Git 能识别的选项列表,请运行

$ git config --helpgit config的手册页(译注:以man命令的显示方式)非常细致地罗列了所有可用的配置项。

core.editor
Git默认会调用你的环境变量editor定义的值作为文本编辑器,如果没有定义的话,会调用Vi来创建和编辑提交以及标签信息, 你可以使用core.editor改变默认编辑器:

$ git config --global core.editor emacs现在无论你的环境变量editor被定义成什么,Git 都会调用Emacs编辑信息。

commit.template
如果把此项指定为你系统上的一个文件,当你提交的时候, Git 会默认使用该文件定义的内容。 例如:你创建了一个模板文件$HOME/.gitmessage.txt,它看起来像这样:

subject linewhat happened[ticket: X]设置commit.template,当运行git commit时, Git 会在你的编辑器中显示以上的内容, 设置commit.template如下:

$ git config --global commit.template $HOME/.gitmessage.txt$ git commit然后当你提交时,在编辑器中显示的提交信息如下:

subject linewhat happened[ticket: X]# Please enter the commit message for your changes. Lines starting# with '#' will be ignored, and an empty message aborts the commit.# On branch master# Changes to be committed:#   (use "git reset HEAD <file>..." to unstage)## modified:   lib/test.rb#~~".git/COMMIT_EDITMSG" 14L, 297C如果你有特定的策略要运用在提交信息上,在系统上创建一个模板文件,设置 Git 默认使用它,这样当提交时,你的策略每次都会被运用。

core.pager
core.pager指定 Git 运行诸如log、diff等所使用的分页器,你能设置成用more或者任何你喜欢的分页器(默认用的是less), 当然你也可以什么都不用,设置空字符串:

$ git config --global core.pager ''这样不管命令的输出量多少,都会在一页显示所有内容。

user.signingkey
如果你要创建经签署的含附注的标签(正如第二章所述),那么把你的GPG签署密钥设置为配置项会更好,设置密钥ID如下:

$ git config --global user.signingkey <gpg-key-id>现在你能够签署标签,从而不必每次运行git tag命令时定义密钥:

$ git tag -s <tag-name>core.excludesfile
正如第二章所述,你能在项目库的.gitignore文件里头用模式来定义那些无需纳入 Git 管理的文件,这样它们不会出现在未跟踪列表, 也不会在你运行git add后被暂存。然而,如果你想用项目库之外的文件来定义那些需被忽略的文件的话,用core.excludesfile 通知 Git 该文件所处的位置,文件内容和.gitignore类似。

help.autocorrect
该配置项只在 Git 1.6.1及以上版本有效,假如你在Git 1.6中错打了一条命令,会显示:

$ git comgit: 'com' is not a git-command. See 'git --help'.Did you mean this?     commit如果你把help.autocorrect设置成1(译注:启动自动修正),那么在只有一个命令被模糊匹配到的情况下,Git 会自动运行该命令。

Git中的着色
Git能够为输出到你终端的内容着色,以便你可以凭直观进行快速、简单地分析,有许多选项能供你使用以符合你的偏好。

color.ui
Git会按照你需要自动为大部分的输出加上颜色,你能明确地规定哪些需要着色以及怎样着色,设置color.ui为true来打开所有的默认终端着色。

$ git config --global color.ui true设置好以后,当输出到终端时,Git 会为之加上颜色。其他的参数还有false和always,false意味着不为输出着色,而always则表明在任何情况下都要着色,即使 Git 命令被重定向到文件或管道。Git 1.5.5版本引进了此项配置,如果你拥有的版本更老,你必须对颜色有关选项各自进行详细地设置。

你会很少用到color.ui = always,在大多数情况下,如果你想在被重定向的输出中插入颜色码,你能传递--color标志给 Git 命令来迫使它这么做,color.ui = true应该是你的首选。

color.*
想要具体到哪些命令输出需要被着色以及怎样着色或者 Git 的版本很老,你就要用到和具体命令有关的颜色配置选项,它们都能被置为true、false或always:

color.branchcolor.diffcolor.interactivecolor.status除此之外,以上每个选项都有子选项,可以被用来覆盖其父设置,以达到为输出的各个部分着色的目的。例如,让diff输出的改变信息以粗体、蓝色前景和黑色背景的形式显示:

$ git config --global color.diff.meta “blue black bold”你能设置的颜色值如:normal、black、red、green、yellow、blue、magenta、cyan、white,正如以上例子设置的粗体属性,想要设置字体属性的话,可以选择如:bold、dim、ul、blink、reverse。

如果你想配置子选项的话,可以参考git config帮助页。

外部的合并与比较工具
虽然 Git 自己实现了diff,而且到目前为止你一直在使用它,但你能够用一个外部的工具替代它,除此以外,你还能用一个图形化的工具来合并和解决冲突从而不必自己手动解决。有一个不错且免费的工具可以被用来做比较和合并工作,它就是P4Merge(译注:Perforce图形化合并工具),我会展示它的安装过程。

P4Merge可以在所有主流平台上运行,现在开始大胆尝试吧。对于向你展示的例子,在Mac和Linux系统上,我会使用路径名,在Windows上,/usr/local/bin应该被改为你环境中的可执行路径。

下载P4Merge:

http://www.perforce.com/perforce/downloads/component.html首先把你要运行的命令放入外部包装脚本中,我会使用Mac系统上的路径来指定该脚本的位置,在其他系统上,它应该被放置在二进制文件p4merge所在的目录中。创建一个merge包装脚本,名字叫作extMerge,让它带参数调用p4merge二进制文件:

$ cat /usr/local/bin/extMerge#!/bin/sh/Applications/p4merge.app/Contents/MacOS/p4merge $*diff包装脚本首先确定传递过来7个参数,随后把其中2个传递给merge包装脚本,默认情况下, Git 传递以下参数给diff:

path old-file old-hex old-mode new-file new-hex new-mode由于你仅仅需要old-file和new-file参数,用diff包装脚本来传递它们吧。

$ cat /usr/local/bin/extDiff #!/bin/sh[ $# -eq 7 ] && /usr/local/bin/extMerge "$2" "$5"确认这两个脚本是可执行的:

$ sudo chmod +x /usr/local/bin/extMerge $ sudo chmod +x /usr/local/bin/extDiff现在来配置使用你自定义的比较和合并工具吧。这需要许多自定义设置:merge.tool通知 Git 使用哪个合并工具;mergetool.*.cmd规定命令运行的方式;mergetool.trustExitCode会通知 Git 程序的退出是否指示合并操作成功;diff.external通知 Git 用什么命令做比较。因此,你能运行以下4条配置命令:

$ git config --global merge.tool extMerge$ git config --global mergetool.extMerge.cmd \    'extMerge "$BASE" "$LOCAL" "$REMOTE" "$MERGED"'$ git config --global mergetool.trustExitCode false$ git config --global diff.external extDiff或者直接编辑~/.gitconfig文件如下:

[merge]  tool = extMerge[mergetool "extMerge"]  cmd = extMerge "$BASE" "$LOCAL" "$REMOTE" "$MERGED"  trustExitCode = false[diff]  external = extDiff设置完毕后,运行diff命令:

$ git diff 32d1776b1^ 32d1776b1命令行居然没有发现diff命令的输出,其实,Git 调用了刚刚设置的P4Merge,它看起来像图7-1这样:



Figure 7-1. P4Merge.
当你设法合并两个分支,结果却有冲突时,运行git mergetool,Git 会调用P4Merge让你通过图形界面来解决冲突。

设置包装脚本的好处是你能简单地改变diff和merge工具,例如把extDiff和extMerge改成KDiff3,要做的仅仅是编辑extMerge脚本文件:

$ cat /usr/local/bin/extMerge#!/bin/sh        /Applications/kdiff3.app/Contents/MacOS/kdiff3 $*现在 Git 会使用KDiff3来做比较、合并和解决冲突。

Git预先设置了许多其他的合并和解决冲突的工具,而你不必设置cmd。可以把合并工具设置为:kdiff3、opendiff、tkdiff、 meld、xxdiff、emerge、vimdiff、gvimdiff。如果你不想用到KDiff3的所有功能,只是想用它来合并,那么kdiff3 正符合你的要求,运行:

$ git config --global merge.tool kdiff3如果运行了以上命令,没有设置extMerge和extDiff文件,Git 会用KDiff3做合并,让通常内设的比较工具来做比较。

格式化与空白
格式化与空白是许多开发人员在协作时,特别是在跨平台情况下,遇到的令人头疼的细小问题。由于编辑器的不同或者Windows程序员在跨平台项目中的文件行尾加入了回车换行符,一些细微的空格变化会不经意地进入大家合作的工作或提交的补丁中。不用怕,Git 的一些配置选项会帮助你解决这些问题。

core.autocrlf
假如你正在Windows上写程序,又或者你正在和其他人合作,他们在Windows上编程,而你却在其他系统上,在这些情况下,你可能会遇到行尾结束符问题。这是因为Windows使用回车和换行两个字符来结束一行,而Mac和Linux只使用换行一个字符。虽然这是小问题,但它会极大地扰乱跨平台协作。

Git可以在你提交时自动地把行结束符CRLF转换成LF,而在签出代码时把LF转换成CRLF。用core.autocrlf来打开此项功能,如果是在Windows系统上,把它设置成true,这样当签出代码时,LF会被转换成CRLF:

$ git config --global core.autocrlf trueLinux或Mac系统使用LF作为行结束符,因此你不想 Git 在签出文件时进行自动的转换;当一个以CRLF为行结束符的文件不小心被引入时你肯定想进行修正,把core.autocrlf设置成input来告诉 Git 在提交时把CRLF转换成LF,签出时不转换:

$ git config --global core.autocrlf input这样会在Windows系统上的签出文件中保留CRLF,会在Mac和Linux系统上,包括仓库中保留LF。

如果你是Windows程序员,且正在开发仅运行在Windows上的项目,可以设置false取消此功能,把回车符记录在库中:

$ git config --global core.autocrlf falsecore.whitespace
Git预先设置了一些选项来探测和修正空白问题,其4种主要选项中的2个默认被打开,另2个被关闭,你可以自由地打开或关闭它们。

默认被打开的2个选项是trailing-space和space-before-tab,trailing-space会查找每行结尾的空格,space-before-tab会查找每行开头的制表符前的空格。

默认被关闭的2个选项是indent-with-non-tab和cr-at-eol,indent-with-non-tab会查找8个以上空格(非制表符)开头的行,cr-at-eol让 Git 知道行尾回车符是合法的。

设置core.whitespace,按照你的意图来打开或关闭选项,选项以逗号分割。通过逗号分割的链中去掉选项或在选项前加-来关闭,例如,如果你想要打开除了cr-at-eol之外的所有选项:

$ git config --global core.whitespace \    trailing-space,space-before-tab,indent-with-non-tab当你运行git diff命令且为输出着色时,Git 探测到这些问题,因此你也许在提交前能修复它们,当你用git apply打补丁时同样也会从中受益。如果正准备运用的补丁有特别的空白问题,你可以让 Git 发警告:

$ git apply --whitespace=warn <patch>或者让 Git 在打上补丁前自动修正此问题:

$ git apply --whitespace=fix <patch>这些选项也能运用于衍合。如果提交了有空白问题的文件但还没推送到上流,你可以运行带有--whitespace=fix选项的rebase来让Git在重写补丁时自动修正它们。

服务器端配置
Git服务器端的配置选项并不多,但仍有一些饶有生趣的选项值得你一看。

receive.fsckObjects
Git默认情况下不会在推送期间检查所有对象的一致性。虽然会确认每个对象的有效性以及是否仍然匹配SHA-1检验和,但 Git 不会在每次推送时都检查一致性。对于 Git 来说,库或推送的文件越大,这个操作代价就相对越高,每次推送会消耗更多时间,如果想在每次推送时 Git 都检查一致性,设置receive.fsckObjects 为true来强迫它这么做:

$ git config --system receive.fsckObjects true现在 Git 会在每次推送生效前检查库的完整性,确保有问题的客户端没有引入**性的数据。

receive.denyNonFastForwards
如果对已经被推送的提交历史做衍合,继而再推送,又或者以其它方式推送一个提交历史至远程分支,且该提交历史没在这个远程分支中,这样的推送会被拒绝。这通常是个很好的禁止策略,但有时你在做衍合并确定要更新远程分支,可以在push命令后加-f标志来强制更新。

要禁用这样的强制更新功能,可以设置receive.denyNonFastForwards:

$ git config --system receive.denyNonFastForwards true稍后你会看到,用服务器端的接收钩子也能达到同样的目的。这个方法可以做更细致的控制,例如:禁用特定的用户做强制更新。

receive.denyDeletes
规避denyNonFastForwards策略的方法之一就是用户删除分支,然后推回新的引用。在更新的 Git 版本中(从1.6.1版本开始),把receive.denyDeletes设置为true:

$ git config --system receive.denyDeletes true这样会在推送过程中阻止删除分支和标签 — 没有用户能够这么做。要删除远程分支,必须从服务器手动删除引用文件。通过用户访问控制列表也能这么做,在本章结尾将会介绍这些有趣的方式。



7.2  Git属性
一些设置项也能被运用于特定的路径中,这样,Git 以对一个特定的子目录或子文件集运用那些设置项。这些设置项被称为 Git 属性,可以在你目录中的.gitattributes文件内进行设置(通常是你项目的根目录),也可以当你不想让这些属性文件和项目文件一同提交时,在.git/info/attributes进行设置。

使用属性,你可以对个别文件或目录定义不同的合并策略,让 Git 知道怎样比较非文本文件,在你提交或签出前让 Git 过滤内容。你将在这部分了解到能在自己的项目中使用的属性,以及一些实例。

二进制文件
你可以用 Git 属性让其知道哪些是二进制文件(以防 Git 没有识别出来),以及指示怎样处理这些文件,这点很酷。例如,一些文本文件是由机器产生的,而且无法比较,而一些二进制文件可以比较 — 你将会了解到怎样让 Git 识别这些文件。

识别二进制文件
一些文件看起来像是文本文件,但其实是作为二进制数据被对待。例如,在Mac上的Xcode项目含有一个以.pbxproj结尾的文件,它是由记录设置项的IDE写到磁盘的JSON数据集(纯文本javascript数据类型)。虽然技术上看它是由ASCII字符组成的文本文件,但你并不认为如此,因为它确实是一个轻量级数据库 — 如果有2人改变了它,你通常无法合并和比较内容,只有机器才能进行识别和操作,于是,你想把它当成二进制文件。

让 Git 把所有pbxproj文件当成二进制文件,在.gitattributes文件中设置如下:

*.pbxproj -crlf -diff现在,Git 会尝试转换和修正CRLF(回车换行)问题,也不会当你在项目中运行git show或git diff时,比较不同的内容。在Git 1.6及之后的版本中,可以用一个宏代替-crlf -diff:

*.pbxproj binary比较二进制文件
在Git 1.6及以上版本中,你能利用 Git 属性来有效地比较二进制文件。可以设置 Git 把二进制数据转换成文本格式,用通常的diff来比较。

这个特性很酷,而且鲜为人知,因此我会结合实例来讲解。首先,要解决的是最令人头疼的问题:对Word文档进行版本控制。很多人对Word文档又恨又爱,如果想对其进行版本控制,你可以把文件加入到 Git 库中,每次修改后提交即可。但这样做没有一点实际意义,因为运行git diff命令后,你只能得到如下的结果:

$ git diffdiff --git a/chapter1.doc b/chapter1.docindex 88839c4..4afcb7c 100644Binary files a/chapter1.doc and b/chapter1.doc differ你不能直接比较两个不同版本的Word文件,除非进行手动扫描,不是吗? Git 属性能很好地解决此问题,把下面的行加到.gitattributes文件:

*.doc diff=word当你要看比较结果时,如果文件扩展名是”doc”,Git 调用”word”过滤器。什么是”word”过滤器呢?其实就是 Git 使用strings 程序,把Word文档转换成可读的文本文件,之后再进行比较:

$ git config diff.word.textconv strings现在如果在两个快照之间比较以.doc结尾的文件,Git 对这些文件运用”word”过滤器,在比较前把Word文件转换成文本文件。

下面展示了一个实例,我把此书的第一章纳入 Git 管理,在一个段落中加入了一些文本后保存,之后运行git diff命令,得到结果如下:

$ git diffdiff --git a/chapter1.doc b/chapter1.docindex c1c8a0a..b93c9e4 100644--- a/chapter1.doc+++ b/chapter1.doc@@ -8,7 +8,8 @@ re going to cover Version Control Systems (VCS) and Git basics re going to cover how to get it and set it up for the first time if you don t already have it on your system. In Chapter Two we will go over basic Git usage - how to use Git for the 80%-s going on, modify stuff and contribute changes. If the book spontaneously+s going on, modify stuff and contribute changes. If the book spontaneously+Let's see if this works.Git 成功且简洁地显示出我增加的文本”Let’s see if this works”。虽然有些瑕疵,在末尾显示了一些随机的内容,但确实可以比较了。如果你能找到或自己写个Word到纯文本的转换器的话,效果可能会更好。strings可以在大部分Mac和Linux系统上运行,所以它是处理二进制格式的第一选择。

你还能用这个方法比较图像文件。当比较时,对JPEG文件运用一个过滤器,它能提炼出EXIF信息 — 大部分图像格式使用的元数据。如果你下载并安装了exiftool程序,可以用它参照元数据把图像转换成文本。比较的不同结果将会用文本向你展示:

$ echo '*.png diff=exif' >> .gitattributes$ git config diff.exif.textconv exiftool如果在项目中替换了一个图像文件,运行git diff命令的结果如下:

diff --git a/image.png b/image.pngindex 88839c4..4afcb7c 100644--- a/image.png+++ b/image.png@@ -1,12 +1,12 @@ ExifTool Version Number         : 7.74-File Size                       : 70 kB-File Modification Date/Time     : 2009:04:21 07:02:45-07:00+File Size                       : 94 kB+File Modification Date/Time     : 2009:04:21 07:02:43-07:00 File Type                       : PNG MIME Type                       : image/png-Image Width                     : 1058-Image Height                    : 889+Image Width                     : 1056+Image Height                    : 827 Bit Depth                       : 8 Color Type                      : RGB with Alpha你会发现文件的尺寸大小发生了改变。

关键字扩展
使用SVN或CVS的开发人员经常要求关键字扩展。在 Git 中,你无法在一个文件被提交后修改它,因为 Git 会先对该文件计算校验和。然而,你可以在签出时注入文本,在提交前删除它。 Git 属性提供了2种方式这么做。

首先,你能够把blob的SHA-1校验和自动注入文件的$Id$字段。如果在一个或多个文件上设置了此字段,当下次你签出分支的时候,Git 用blob的SHA-1值替换那个字段。注意,这不是提交对象的SHA校验和,而是blob本身的校验和:

$ echo '*.txt ident' >> .gitattributes$ echo '$Id$' > test.txt下次签出文件时,Git 入了blob的SHA值:

$ rm text.txt$ git checkout -- text.txt$ cat test.txt$Id: 42812b7653c7b88933f8a9d6cad0ca16714b9bb3 $然而,这样的显示结果没有多大的实际意义。这个SHA的值相当地随机,无法区分日期的前后,所以,如果你在CVS或Subversion中用过关键字替换,一定会包含一个日期值。

因此,你能写自己的过滤器,在提交文件到暂存区或签出文件时替换关键字。有2种过滤器,”clean”和”smudge”。在 .gitattributes文件中,你能对特定的路径设置一个过滤器,然后设置处理文件的脚本,这些脚本会在文件签出前(”smudge”,见图 7-2)和提交到暂存区前(”clean”,见图7-3)被调用。这些过滤器能够做各种有趣的事。



图7-2. 签出时,“smudge”过滤器被触发。


图7-3. 提交到暂存区时,“clean”过滤器被触发。
这里举一个简单的例子:在暂存前,用indent(缩进)程序过滤所有C源代码。在.gitattributes文件中设置”indent”过滤器过滤*.c文件:

*.c     filter=indent然后,通过以下配置,让 Git 知道”indent”过滤器在遇到”smudge”和”clean”时分别该做什么:

$ git config --global filter.indent.clean indent$ git config --global filter.indent.smudge cat于是,当你暂存*.c文件时,indent程序会被触发,在把它们签出之前,cat程序会被触发。但cat程序在这里没什么实际作用。这样的组合,使C源代码在暂存前被indent程序过滤,非常有效。

另一个例子是类似RCS的$Date$关键字扩展。为了演示,需要一个小脚本,接受文件名参数,得到项目的最新提交日期,最后把日期写入该文件。下面用Ruby脚本来实现:

#! /usr/bin/env rubydata = STDIN.readlast_date = `git log --pretty=format:"%ad" -1`puts data.gsub('$Date$', '$Date: ' + last_date.to_s + '$')该脚本从git log命令中得到最新提交日期,找到文件中的所有$Date$字符串,最后把该日期填充到$Date$字符串中 — 此脚本很简单,你可以选择你喜欢的编程语言来实现。把该脚本命名为expand_date,放到正确的路径中,之后需要在 Git 中设置一个过滤器(dater),让它在签出文件时调用expand_date,在暂存文件时用Perl清除之:

$ git config filter.dater.smudge expand_date$ git config filter.dater.clean 'perl -pe "s/\\\$Date[^\\\$]*\\\$/\\\$Date\\\$/"'这个Perl小程序会删除$Date$字符串里多余的字符,恢复$Date$原貌。到目前为止,你的过滤器已经设置完毕,可以开始测试了。打开一个文件,在文件中输入$Date$关键字,然后设置 Git 属性:

$ echo '# $Date$' > date_test.txt$ echo 'date*.txt filter=dater' >> .gitattributes如果暂存该文件,之后再签出,你会发现关键字被替换了:

$ git add date_test.txt .gitattributes$ git commit -m "Testing date expansion in Git"$ rm date_test.txt$ git checkout date_test.txt$ cat date_test.txt# $Date: Tue Apr 21 07:26:52 2009 -0700$虽说这项技术对自定义应用来说很有用,但还是要小心,因为.gitattributes文件会随着项目一起提交,而过滤器(例如:dater)不会,所以,过滤器不会在所有地方都生效。当你在设计这些过滤器时要注意,即使它们无法正常工作,也要让整个项目运作下去。

导出仓库
Git属性在导出项目归档时也能发挥作用。

export-ignore
当产生一个归档时,可以设置 Git 不导出某些文件和目录。如果你不想在归档中包含一个子目录或文件,但想他们纳入项目的版本管理中,你能对应地设置export-ignore属性。

例如,在test/子目录中有一些测试文件,在项目的压缩包中包含他们是没有意义的。因此,可以增加下面这行到 Git 属性文件中:

test/ export-ignore现在,当运行git archive来创建项目的压缩包时,那个目录不会在归档中出现。

export-subst
还能对归档做一些简单的关键字替换。在第2章中已经可以看到,可以以--pretty=format形式的简码在任何文件中放入$Format 字符串。例如,如果想在项目中包含一个叫作LAST_COMMIT的文件,当运行git archive时,最后提交日期自动地注入进该文件,可以这样设置:

$ echo 'Last commit date: $Format:%cd$' > LAST_COMMIT$ echo "LAST_COMMIT export-subst" >> .gitattributes$ git add LAST_COMMIT .gitattributes$ git commit -am 'adding LAST_COMMIT file for archives'运行git archive后,打开该文件,会发现其内容如下:

$ cat LAST_COMMITLast commit date: $Format:Tue Apr 21 08:38:48 2009 -0700$合并策略
通过 Git 属性,还能对项目中的特定文件使用不同的合并策略。一个非常有用的选项就是,当一些特定文件发生冲突,Git 会尝试合并他们,而使用你这边的合并。

如果项目的一个分支有歧义或比较特别,但你想从该分支合并,而且需要忽略其中某些文件,这样的合并策略是有用的。例如,你有一个数据库设置文件database.xml,在2个分支中他们是不同的,你想合并一个分支到另一个,而不弄乱该数据库文件,可以设置属性如下:

database.xml merge=ours如果合并到另一个分支,database.xml文件不会有合并冲突,显示如下:

$ git merge topicAuto-merging database.xmlMerge made by recursive.这样,database.xml会保持原样。

作者: compare2000    时间: 2014-05-22 17:45
7.3  Git挂 钩
和其他版本控制系统一样,当某些重要事件发生时,Git 以调用自定义脚本。有两组挂 钩:客户端和服务器端。客户端挂 钩用于客户端的操作,如提交和合并。服务器端挂 钩用于 Git 服务器端的操作,如接收被推送的提交。你可以随意地使用这些挂 钩,下面会讲解其中一些。

安装一个挂 钩
挂 钩都被存储在 Git 目录下的hooks子目录中,即大部分项目中的.git/hooks。 Git 默认会放置一些脚本样本在这个目录中,除了可以作为挂 钩使用,这些样本本身是可以独立使用的。所有的样本都是shell脚本,其中一些还包含了Perl的脚本,不过,任何正确命名的可执行脚本都可以正常使用 — 可以用Ruby或Python,或其他。在Git 1.6版本之后,这些样本名都是以.sample结尾,因此,你必须重新命名。在Git 1.6版本之前,这些样本名都是正确的,但这些样本不是可执行文件。

把一个正确命名且可执行的文件放入 Git 目录下的hooks子目录中,可以激活该挂 钩脚本,因此,之后他一直会被 Git 调用。随后会讲解主要的挂 钩脚本。

客户端挂 钩
有许多客户端挂 钩,以下把他们分为:提交工作流挂 钩、电子邮件工作流挂 钩及其他客户端挂 钩。

提交工作流挂 钩
有 4个挂 钩被用来处理提交的过程。pre-commit挂 钩在键入提交信息前运行,被用来检查即将提交的快照,例如,检查是否有东西被遗漏,确认测试是否运行,以及检查代码。当从该挂  钩返回非零值时,Git 放弃此次提交,但可以用git commit --no-verify来忽略。该挂  钩可以被用来检查代码错误(运行类似lint的程序),检查尾部空白(默认挂 钩是这么做的),检查新方法(译注:程序的函数)的说明。

prepare-commit-msg挂 钩在提交信息编辑器显示之前,默认信息被创建之后运行。因此,可以有机会在提交作者看到默认信息前进行编辑。该挂 钩接收一些选项:拥有提交信息的文件路径,提交类型,如果是一次修订的话,提交的SHA-1校验和。该挂 钩对通常的提交来说不是很有用,只在自动产生的默认提交信息的情况下有作用,如提交信息模板、合并、压缩和修订提交等。可以和提交模板配合使用,以编程的方式插入信息。

commit-msg挂 钩接收一个参数,此参数是包含最近提交信息的临时文件的路径。如果该挂 钩脚本以非零退出,Git 放弃提交,因此,可以用来在提交通过前验证项目状态或提交信息。本章上一小节已经展示了使用该挂 钩核对提交信息是否符合特定的模式。

post-commit挂 钩在整个提交过程完成后运行,他不会接收任何参数,但可以运行git log -1 HEAD来获得最后的提交信息。总之,该挂 钩是作为通知之类使用的。

提交工作流的客户端挂 钩脚本可以在任何工作流中使用,他们经常被用来实施某些策略,但值得注意的是,这些脚本在clone期间不会被传送。可以在服务器端实施策略来拒绝不符合某些策略的推送,但这完全取决于开发者在客户端使用这些脚本的情况。所以,这些脚本对开发者是有用的,由他们自己设置和维护,而且在任何时候都可以覆盖或修改这些脚本。

E-mail工作流挂 钩
有3个可用的客户端挂  钩用于e-mail工作流。当运行git am命令时,会调用他们,因此,如果你没有在工作流中用到此命令,可以跳过本节。如果你通过e-mail接收由git format-patch产生的补丁,这些挂  钩也许对你有用。

首先运行的是applypatch-msg挂  钩,他接收一个参数:包含被建议提交信息的临时文件名。如果该脚本非零退出,Git 放弃此补丁。可以使用这个脚本确认提交信息是否被正确格式化,或让脚本编辑信息以达到标准化。

下一个在git am运行期间调用是pre-applypatch挂  钩。该挂  钩不接收参数,在补丁被运用之后运行,因此,可以被用来在提交前检查快照。你能用此脚本运行测试,检查工作树。如果有些什么遗漏,或测试没通过,脚本会以非零退出,放弃此次git am的运行,补丁不会被提交。

最后在git am运行期间调用的是post-applypatch挂  钩。你可以用他来通知一个小组或获取的补丁的作者,但无法阻止打补丁的过程。

其他客户端挂  钩
pre- rebase挂  钩在衍合前运行,脚本以非零退出可以中止衍合的过程。你可以使用这个挂  钩来禁止衍合已经推送的提交对象,Git pre- rebase挂  钩样本就是这么做的。该样本假定next是你定义的分支名,因此,你可能要修改样本,把next改成你定义过且稳定的分支名。

在git checkout成功运行后,post-checkout挂  钩会被调用。他可以用来为你的项目环境设置合适的工作目录。例如:放入大的二进制文件、自动产生的文档或其他一切你不想纳入版本控制的文件。

最后,在merge命令成功执行后,post-merge挂  钩会被调用。他可以用来在 Git 无法跟踪的工作树中恢复数据,诸如权限数据。该挂  钩同样能够验证在 Git 控制之外的文件是否存在,因此,当工作树改变时,你想这些文件可以被复制。

服务器端挂  钩
除了客户端挂  钩,作为系统管理员,你还可以使用两个服务器端的挂  钩对项目实施各种类型的策略。这些挂  钩脚本可以在提交对象推送到服务器前被调用,也可以在推送到服务器后被调用。推送到服务器前调用的挂  钩可以在任何时候以非零退出,拒绝推送,返回错误消息给客户端,还可以如你所愿设置足够复杂的推送策略。

pre-receive 和 post-receive
处理来自客户端的推送(push)操作时最先执行的脚本就是 pre-receive 。它从标准输入(stdin)获取被推送引用的列表;如果它退出时的返回值不是0,所有推送内容都不会被接受。利用此挂  钩脚本可以实现类似保证最新的索引中不包含非fast-forward类型的这类效果;抑或检查执行推送操作的用户拥有创建,删除或者推送的权限或者他是否对将要修改的每一个文件都有访问权限。

post-receive 挂  钩在整个过程完结以后运行,可以用来更新其他系统服务或者通知用户。它接受与 pre-receive 相同的标准输入数据。应用实例包括给某邮件列表发信,通知实时整合数据的服务器,或者更新软件项目的问题追踪系统 —— 甚至可以通过分析提交信息来决定某个问题是否应该被开启,修改或者关闭。该脚本无法组织推送进程,不过客户端在它完成运行之前将保持连接状态;所以在用它作一些消耗时间的操作之前请三思。

update
update 脚本和 pre-receive 脚本十分类似。不同之处在于它会为推送者更新的每一个分支运行一次。假如推送者同时向多个分支推送内容,pre-receive 只运行一次,相比之下 update 则会为每一个更新的分支运行一次。它不会从标准输入读取内容,而是接受三个参数:索引的名字(分支),推送前索引指向的内容的 SHA-1 值,以及用户试图推送内容的 SHA-1 值。如果 update 脚本以退出时返回非零值,只有相应的那一个索引会被拒绝;其余的依然会得到更新。

作者: compare2000    时间: 2014-05-22 17:46
7.4  Git 强制策略实例
在本节中,我们应用前面学到的知识建立这样一个Git 工作流程:检查提交信息的格式,只接受纯fast-forward内容的推送,并且指定用户只能修改项目中的特定子目录。我们将写一个客户端角本来提示开发人员他们推送的内容是否会被拒绝,以及一个服务端脚本来实际执行这些策略。

这些脚本使用 Ruby 写成,一半由于它是作者倾向的脚本语言,另外作者觉得它是最接近伪代码的脚本语言;因而即便你不使用 Ruby 也能大致看懂。不过任何其他语言也一样适用。所有 Git 自带的样例脚本都是用 Perl 或 Bash 写的。所以从这些脚本中能找到相当多的这两种语言的挂  钩样例。

服务端挂  钩
所有服务端的工作都在hooks(挂  钩)目录的 update(更新)脚本中制定。update 脚本为每一个得到推送的分支运行一次;它接受推送目标的索引,该分支原来指向的位置,以及被推送的新内容。如果推送是通过 SSH 进行的,还可以获取发出此次操作的用户。如果设定所有操作都通过公匙授权的单一帐号(比如"git")进行,就有必要通过一个 shell 包装依据公匙来判断用户的身份,并且设定环境变量来表示该用户的身份。下面假设尝试连接的用户储存在$USER 环境变量里,我们的 update 脚本首先搜集一切需要的信息:

#!/usr/bin/env ruby$refname = ARGV[0]$oldrev  = ARGV[1]$newrev  = ARGV[2]$user    = ENV['USER']puts "Enforcing Policies... \n(#{$refname}) (#{$oldrev[0,6]}) (#{$newrev[0,6]})"没错,我在用全局变量。别鄙视我——这样比较利于演示过程。

指定特殊的提交信息格式
我们的第一项任务是指定每一条提交信息都必须遵循某种特殊的格式。作为演示,假定每一条信息必须包含一条形似 “ref: 1234” 这样的字符串,因为我们需要把每一次提交和项目的问题追踪系统。我们要逐一检查每一条推送上来的提交内容,看看提交信息是否包含这么一个字符串,然后,如果该提交里不包含这个字符串,以非零返回值退出从而拒绝此次推送。

把 $newrev 和 $oldrev 变量的值传给一个叫做 git rev-list 的 Git plumbing 命令可以获取所有提交内容的 SHA-1 值列表。git rev-list 基本类似git log 命令,但它默认只输出 SHA-1 值而已,没有其他信息。所以要获取由 SHA 值表示的从一次提交到另一次提交之间的所有 SHA 值,可以运行:

$ git rev-list 538c33..d14fc7d14fc7c847ab946ec39590d87783c69b031bdfb79f585da4401b0a3999e84113824d15245c13f0be234071a1be950e2a8d078e6141f5cd20c1e61ad3dfa04c9ef3d5197182f13fb5b9b1fb7717d2222a17716ec0f1ff5c77eff40b7fe912f9f6cfd0e475截取这些输出内容,循环遍历其中每一个 SHA 值,找出与之对应的提交信息,然后用正则表达式来测试该信息包含的格式话的内容。

下面要搞定如何从所有的提交内容中提取出提交信息。使用另一个叫做 git cat-file 的 Git plumbing 工具可以获得原始的提交数据。我们将在第九章了解到这些 plumbing 工具的细节;现在暂时先看一下这条命令的输出:

$ git cat-file commit ca82a6tree cfda3bf379e4f8dba8717dee55aab78aef7f4dafparent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7author Scott Chacon <schacon@gmail.com> 1205815931 -0700committer Scott Chacon <schacon@gmail.com> 1240030591 -0700changed the version number通过 SHA-1 值获得提交内容中的提交信息的一个简单办法是找到提交的第一行,然后取从它往后的所有内容。可以使用 Unix 系统的 sed 命令来实现该效果:

$ git cat-file commit ca82a6 | sed '1,/^$/d'changed the version number这条咒语从每一个待提交内容里提取提交信息,并且会在提取信息不符合要求的情况下退出。为了退出脚本和拒绝此次推送,返回一个非零值。整个脚本大致如下:

$regex = /\[ref: (\d+)\]/# 指定提交信息格式def check_message_format  missed_revs = `git rev-list #{$oldrev}..#{$newrev}`.split("\n")  missed_revs.each do |rev|    message = `git cat-file commit #{rev} | sed '1,/^$/d'`    if !$regex.match(message)      puts "[POLICY] Your message is not formatted correctly"      exit 1    end  endendcheck_message_format把这一段放在 update 脚本里,所有包含不符合指定规则的提交都会遭到拒绝。

实现基于用户的访问权限控制列表(ACL)系统
假设你需要添加一个使用访问权限控制列表的机制来指定哪些用户对项目的哪些部分有推送权限。某些用户具有全部的访问权,其他人只对某些子目录或者特定的文件具有推送权限。要搞定这一点,所有的规则将被写入一个位于服务器的原始 Git 仓库的acl 文件。我们让 update 挂  钩检阅这些规则,审视推送的提交内容中需要修改的所有文件,然后决定执行推送的用户是否对所有这些文件都有权限。

我们首先要创建这个列表。这里使用的格式和 CVS 的 ACL 机制十分类似:它由若干行构成,第一项内容是 avail 或者 unavail,接着是逗号分隔的规则生效用户列表,最后一项是规则生效的目录(空白表示开放访问)。这些项目由 | 字符隔开。

下例中,我们指定几个管理员,几个对 doc 目录具有权限的文档作者,以及一个对 lib 和 tests 目录具有权限的开发人员,相应的 ACL 文件如下:

avail|nickh,pjhyett,defunkt,tpwavail|usinclair,cdickens,ebronte|docavail|schacon|libavail|schacon|tests首先把这些数据读入你编写的数据结构。本例中,为保持简洁,我们暂时只实现 avail 的规则(译注:也就是省略了 unavail 部分)。下面这个方法生成一个关联数组,它的主键是用户名,值是一个该用户有写权限的所有目录组成的数组:

def get_acl_access_data(acl_file)  # read in ACL data  acl_file = File.read(acl_file).split("\n").reject { |line| line == '' }  access = {}  acl_file.each do |line|    avail, users, path = line.split('|')    next unless avail == 'avail'    users.split(',').each do |user|      access[user] ||= []      access[user] << path    end  end  accessend针对之前给出的 ACL 规则文件,这个 get_acl_access_data 方法返回的数据结构如下:

{"defunkt"=>[nil], "tpw"=>[nil], "nickh"=>[nil], "pjhyett"=>[nil], "schacon"=>["lib", "tests"], "cdickens"=>["doc"], "usinclair"=>["doc"], "ebronte"=>["doc"]}搞定了用户权限的数据,下面需要找出哪些位置将要被提交的内容修改,从而确保试图推送的用户对这些位置有全部的权限。

使用 git log 的 --name-only 选项(在第二章里简单的提过)我们可以轻而易举的找出一次提交里修改的文件:

$ git log -1 --name-only --pretty=format:'' 9f585dREADMElib/test.rb使用 get_acl_access_data 返回的 ACL 结构来一一核对每一次提交修改的文件列表,就能找出该用户是否有权限推送所有的提交内容:

# 仅允许特定用户修改项目中的特定子目录def check_directory_perms  access = get_acl_access_data('acl')  # 检查是否有人在向他没有权限的地方推送内容  new_commits = `git rev-list #{$oldrev}..#{$newrev}`.split("\n")  new_commits.each do |rev|    files_modified = `git log -1 --name-only --pretty=format:'' #{rev}`.split("\n")    files_modified.each do |path|      next if path.size == 0      has_file_access = false      access[$user].each do |access_path|        if !access_path  # 用户拥有完全访问权限          || (path.index(access_path) == 0) # 或者对此位置有访问权限          has_file_access = true         end      end      if !has_file_access        puts "[POLICY] You do not have access to push to #{path}"        exit 1      end    end  end  endcheck_directory_perms以上的大部分内容应该都比较容易理解。通过 git rev-list 获取推送到服务器内容的提交列表。然后,针对其中每一项,找出它试图修改的文件然后确保执行推送的用户对这些文件具有权限。一个不太容易理解的 Ruby 技巧石path.index(access_path) ==0 这句,它的返回真值如果路径以 access_path 开头——这是为了确保access_path 并不是只在允许的路径之一,而是所有准许全选的目录都在该目录之下。

现在你的用户没法推送带有不正确的提交信息的内容,也不能在准许他们访问范围之外的位置做出修改。

只允许 Fast-Forward 类型的推送
剩下的最后一项任务是指定只接受 fast-forward 的推送。在 Git 1.6 或者更新版本里,只需要设定 receive.denyDeletes 和receive.denyNonFastForwards 选项就可以了。但是通过挂  钩的实现可以在旧版本的 Git 上工作,并且通过一定的修改它它可以做到只针对某些用户执行,或者更多以后可能用的到的规则。

检查这一项的逻辑是看看提交里是否包含从旧版本里能找到但在新版本里却找不到的内容。如果没有,那这是一次纯 fast-forward 的推送;如果有,那我们拒绝此次推送:

# 只允许纯 fast-forward 推送def check_fast_forward  missed_refs = `git rev-list #{$newrev}..#{$oldrev}`  missed_ref_count = missed_refs.split("\n").size  if missed_ref_count > 0    puts "[POLICY] Cannot push a non fast-forward reference"    exit 1  endendcheck_fast_forward一切都设定好了。如果现在运行 chmod u+x .git/hooks/update —— 修改包含以上内容文件的权限,然后尝试推送一个包含非 fast-forward 类型的索引,会得到一下提示:

$ git push -f origin masterCounting objects: 5, done.Compressing objects: 100% (3/3), done.Writing objects: 100% (3/3), 323 bytes, done.Total 3 (delta 1), reused 0 (delta 0)Unpacking objects: 100% (3/3), done.Enforcing Policies... (refs/heads/master) (8338c5) (c5b616)[POLICY] Cannot push a non-fast-forward referenceerror: hooks/update exited with error code 1error: hook declined to update refs/heads/masterTo git@gitserver:project.git ! [remote rejected] master -> master (hook declined)error: failed to push some refs to 'git@gitserver:project.git'这里有几个有趣的信息。首先,我们可以看到挂  钩运行的起点:

Enforcing Policies... (refs/heads/master) (fb8c72) (c56860)注意这是从 update 脚本开头输出到标准你输出的。所有从脚本输出的提示都会发送到客户端,这点很重要。

下一个值得注意的部分是错误信息。

[POLICY] Cannot push a non fast-forward referenceerror: hooks/update exited with error code 1error: hook declined to update refs/heads/master第一行是我们的脚本输出的,在往下是 Git 在告诉我们 update 脚本退出时返回了非零值因而推送遭到了拒绝。最后一点:

To git@gitserver:project.git ! [remote rejected] master -> master (hook declined)error: failed to push some refs to 'git@gitserver:project.git'我们将为每一个被挂  钩拒之门外的索引受到一条远程信息,解释它被拒绝是因为一个挂  钩的原因。

而且,如果那个 ref 字符串没有包含在任何的提交里,我们将看到前面脚本里输出的错误信息:

[POLICY] Your message is not formatted correctly又或者某人想修改一个自己不具备权限的文件然后推送了一个包含它的提交,他将看到类似的提示。比如,一个文档作者尝试推送一个修改到 lib 目录的提交,他会看到

[POLICY] You do not have access to push to lib/test.rb全在这了。从这里开始,只要 update 脚本存在并且可执行,我们的仓库永远都不会遭到回转或者包含不符合要求信息的提交内容,并且用户都被锁在了沙箱里面。

客户端挂  钩
这种手段的缺点在于用户推送内容遭到拒绝后几乎无法避免的抱怨。辛辛苦苦写成的代码在最后时刻惨遭拒绝是十分悲剧切具迷惑性的;更可怜的是他们不得不修改提交历史来解决问题,这怎么也算不上王道。

逃离这种两难境地的法宝是给用户一些客户端的挂  钩,在他们作出可能悲剧的事情的时候给以警告。然后呢,用户们就能在提交–问题变得更难修正之前解除隐患。由于挂  钩本身不跟随克隆的项目副本分发,所以必须通过其他途径把这些挂  钩分发到用户的 .git/hooks 目录并设为可执行文件。虽然可以在相同或单独的项目内 容里加入并分发它们,全自动的解决方案是不存在的。

首先,你应该在每次提交前核查你的提交注释信息,这样你才能确保服务器不会因为不合条件的提交注释信息而拒绝你的更改。为了达到这个目的,你可以增加’commit-msg’挂  钩。如果你使用该挂  钩来阅读作为第一个参数传递给git的提交注释信息,并且与规定的模式作对比,你就可以使git在提交注释信息不符合条件的情况下,拒绝执行提交。

#!/usr/bin/env rubymessage_file = ARGV[0]message = File.read(message_file)$regex = /\[ref: (\d+)\]/if !$regex.match(message)  puts "[POLICY] Your message is not formatted correctly"  exit 1end如果这个脚本放在这个位置 (.git/hooks/commit-msg) 并且是可执行的, 并且你的提交注释信息不是符合要求的,你会看到:

$ git commit -am 'test'[POLICY] Your message is not formatted correctly在这个实例中,提交没有成功。然而如果你的提交注释信息是符合要求的,git会允许你提交:

$ git commit -am 'test [ref: 132]'[master e05c914] test [ref: 132] 1 files changed, 1 insertions(+), 0 deletions(-)接下来我们要保证没有修改到 ACL 允许范围之外的文件。加入你的 .git 目录里有前面使用过的 ACL 文件,那么以下的 pre-commit 脚本将把里面的规定执行起来:

#!/usr/bin/env ruby$user    = ENV['USER']# [ insert acl_access_data method from above ]# 只允许特定用户修改项目重特定子目录的内容def check_directory_perms  access = get_acl_access_data('.git/acl')  files_modified = `git diff-index --cached --name-only HEAD`.split("\n")  files_modified.each do |path|    next if path.size == 0    has_file_access = false    access[$user].each do |access_path|    if !access_path || (path.index(access_path) == 0)      has_file_access = true    end    if !has_file_access      puts "[POLICY] You do not have access to push to #{path}"      exit 1    end  endendcheck_directory_perms这和服务端的脚本几乎一样,除了两个重要区别。第一,ACL 文件的位置不同,因为这个脚本在当前工作目录运行,而非 Git 目录。ACL 文件的目录必须从

access = get_acl_access_data('acl')修改成:

access = get_acl_access_data('.git/acl')另一个重要区别是获取被修改文件列表的方式。在服务端的时候使用了查看提交纪录的方式,可是目前的提交都还没被记录下来呢,所以这个列表只能从暂存区域获取。和原来的

files_modified = `git log -1 --name-only --pretty=format:'' #{ref}`不同,现在要用

files_modified = `git diff-index --cached --name-only HEAD`不同的就只有这两点——除此之外,该脚本完全相同。一个小陷阱在于它假设在本地运行的账户和推送到远程服务端的相同。如果这二者不一样,则需要手动设置一下 $user 变量。

最后一项任务是检查确认推送内容中不包含非 fast-forward 类型的索引,不过这个需求比较少见。要找出一个非 fast-forward 类型的索引,要么衍合超过某个已经推送过的提交,要么从本地不同分支推送到远程相同的分支上。

既然服务器将给出无法推送非 fast-forward 内容的提示,而且上面的挂  钩也能阻止强制的推送,唯一剩下的潜在问题就是衍合一次已经推送过的提交内容。

下面是一个检查这个问题的 pre-rabase 脚本的例子。它获取一个所有即将重写的提交内容的列表,然后检查它们是否在远程的索引里已经存在。一旦发现某个提交可以从远程索引里衍变过来,它就放弃衍合操作:

#!/usr/bin/env rubybase_branch = ARGV[0]if ARGV[1]  topic_branch = ARGV[1]else  topic_branch = "HEAD"endtarget_shas = `git rev-list #{base_branch}..#{topic_branch}`.split("\n")remote_refs = `git branch -r`.split("\n").map { |r| r.strip }target_shas.each do |sha|  remote_refs.each do |remote_ref|    shas_pushed = `git rev-list ^#{sha}^@ refs/remotes/#{remote_ref}`    if shas_pushed.split(“\n”).include?(sha)      puts "[POLICY] Commit #{sha} has already been pushed to #{remote_ref}"      exit 1    end  endend这个脚本利用了一个第六章“修订版本选择”一节中不曾提到的语法。通过这一句可以获得一个所有已经完成推送的提交的列表:

git rev-list ^#{sha}^@ refs/remotes/#{remote_ref}SHA^@ 语法解析该次提交的所有祖先。这里我们从检查远程最后一次提交能够衍变获得但从所有我们尝试推送的提交的 SHA 值祖先无法衍变获得的提交内容——也就是 fast-forward 的内容。

这个解决方案的硬伤在于它有可能很慢而且常常没有必要——只要不用 -f 来强制推送,服务器会自动给出警告并且拒绝推送内容。然而,这是个不错的练习而且理论上能帮助用户避免一次将来不得不折回来修改的衍合操作。



7.5  总结
你已经见识过绝大多数通过自定义 Git 客户端和服务端来来适应自己工作流程和项目内容的方式了。无论你创造出了什么样的工作流程,Git 都能用的顺手。

作者: compare2000    时间: 2014-05-22 17:52
Git详解八:Git与其他系统  
Git 与其他系统
世界不是完美的。大多数时候,将所有接触到的项目全部转向 Git 是不可能的。有时我们不得不为某个项目使用其他的版本控制系统(VCS, Version Control System ),其中比较常见的是 Subversion 。你将在本章的第一部分学习使用git svn ,Git 为 Subversion 附带的双向桥接工具。

或许现在你已经在考虑将先前的项目转向 Git 。本章的第二部分将介绍如何将项目迁移到 Git:先介绍从 Subversion 的迁移,然后是 Perforce,最后介绍如何使用自定义的脚本进行非标准的导入。



8.1  Git 与 Subversion
当前,大多数开发中的开源项目以及大量的商业项目都使用 Subversion 来管理源码。作为最流行的开源版本控制系统,Subversion 已经存在了接近十年的时间。它在许多方面与 CVS 十分类似,后者是前者出现之前代码控制世界的霸主。

Git 最为重要的特性之一是名为 git svn 的 Subversion 双向桥接工具。该工具把 Git 变成了 Subversion 服务的客户端,从而让你在本地享受到 Git 所有的功能,而后直接向 Subversion 服务器推送内容,仿佛在本地使用了 Subversion 客户端。也就是说,在其他人忍受古董的同时,你可以在本地享受分支合并,使暂存区域,衍合以及 单项挑拣等等。这是个让 Git 偷偷潜入合作开发环境的好东西,在帮助你的开发同伴们提高效率的同时,它还能帮你劝说团队让整个项目框架转向对 Git 的支持。这个 Subversion 之桥是通向分布式版本控制系统(DVCS, Distributed VCS )世界的神奇隧道。

作者: compare2000    时间: 2014-05-22 17:52
git svn
Git 中所有 Subversion 桥接命令的基础是 git svn 。所有的命令都从它开始。相关的命令数目不少,你将通过几个简单的工作流程了解到其中常见的一些。

值得警戒的是,在使用 git svn 的时候,你实际是在与 Subversion 交互,Git 比它要高级复杂的多。尽管可以在本地随意的进行分支和合并,最好还是通过衍合保持线性的提交历史,尽量避免类似与远程 Git 仓库动态交互这样的操作。

避免修改历史再重新推送的做法,也不要同时推送到并行的 Git 仓库来试图与其他 Git 用户合作。Subersion 只能保存单一的线性提交历史,一不小心就会被搞糊涂。合作团队中同时有人用 SVN 和 Git,一定要确保所有人都使用 SVN 服务来协作——这会让生活轻松很多。

初始设定
为了展示功能,先要一个具有写权限的 SVN 仓库。如果想尝试这个范例,你必须复制一份其中的测试仓库。比较简单的做法是使用一个名为 svnsync 的工具。较新的 Subversion 版本中都带有该工具,它将数据编码为用于网络传输的格式。

要尝试本例,先在本地新建一个 Subversion 仓库:

$ mkdir /tmp/test-svn$ svnadmin create /tmp/test-svn然后,允许所有用户修改 revprop —— 简单的做法是添加一个总是以 0 作为返回值的 pre-revprop-change 脚本:

$ cat /tmp/test-svn/hooks/pre-revprop-change #!/bin/shexit 0;$ chmod +x /tmp/test-svn/hooks/pre-revprop-change现在可以调用 svnsync init 加目标仓库,再加源仓库的格式来把该项目同步到本地了:

$ svnsync init file:///tmp/test-svn http://progit-example.googlecode.com/svn/ 这将建立进行同步所需的属性。可以通过运行以下命令来克 隆代码:

$ svnsync sync file:///tmp/test-svnCommitted revision 1.Copied properties for revision 1.Committed revision 2.Copied properties for revision 2.Committed revision 3....别看这个操作只花掉几分钟,要是你想把源仓库复制到另一个远程仓库,而不是本地仓库,那将花掉接近一个小时,尽管项目中只有不到 100 次的提交。 Subversion 每次只复制一次修改,把它推送到另一个仓库里,然后周而复始——惊人的低效,但是我们别无选择。

入门
有了可以写入的 Subversion 仓库以后,就可以尝试一下典型的工作流程了。我们从 git svn clone 命令开始,它会把整个 Subversion 仓库导入到一个本地的 Git 仓库中。提醒一下,这里导入的是一个货真价实的 Subversion 仓库,所以应该把下面的file:///tmp/test-svn 换成你所用的 Subversion 仓库的 URL:

$ git svn clone file:///tmp/test-svn -T trunk -b branches -t tagsInitialized empty Git repository in /Users/schacon/projects/testsvnsync/svn/.git/r1 = b4e387bc68740b5af56c2a5faf4003ae42bd135c (trunk)      A    m4/acx_pthread.m4      A    m4/stl_hash.m4...r75 = d1957f3b307922124eec6314e15bcda59e3d9610 (trunk)Found possible branch point: file:///tmp/test-svn/trunk => \    file:///tmp/test-svn /branches/my-calc-branch, 75Found branch parent: (my-calc-branch) d1957f3b307922124eec6314e15bcda59e3d9610Following parent with do_switchSuccessfully followed parentr76 = 8624824ecc0badd73f40ea2f01fce51894189b01 (my-calc-branch)Checked out HEAD: file:///tmp/test-svn/branches/my-calc-branch r76这相当于针对所提供的 URL 运行了两条命令—— git svn init 加上 gitsvn fetch 。可能会花上一段时间。我们所用的测试项目仅仅包含 75 次提交并且它的代码量不算大,所以只有几分钟而已。不过,Git 仍然需要提取每一个版本,每次一个,再逐个提交。对于一个包含成百上千次提交的项目,花掉的时间则可能是几小时甚至数天。

-T trunk -b branches -t tags 告诉 Git 该 Subversion 仓库遵循了基本的分支和标签命名法则。如果你的主干(译注:trunk,相当于非分布式版本控制里的master分支,代表开发的主线),分支或者标签以不同的方式命名,则应做出相应改变。由于该法则的常见性,可以使用-s 来代替整条命令,它意味着标准布局(s 是 Standard layout 的首字母),也就是前面选项的内容。下面的命令有相同的效果:

$ git svn clone file:///tmp/test-svn -s现在,你有了一个有效的 Git 仓库,包含着导入的分支和标签:

$ git branch -a* master  my-calc-branch  tags/2.0.2  tags/release-2.0.1  tags/release-2.0.2  tags/release-2.0.2rc1  trunk值得注意的是,该工具分配命名空间时和远程引用的方式不尽相同。克 隆普通的 Git 仓库时,可以以 origin/[branch] 的形式获取远程服务器上所有可用的分支——分配到远程服务的名称下。然而git svn 假定不存在多个远程服务器,所以把所有指向远程服务的引用不加区分的保存下来。可以用 Git 探测命令 show-ref 来查看所有引用的全名。

$ git show-ref1cbd4904d9982f386d87f88fce1c24ad7c0f0471 refs/heads/masteraee1ecc26318164f355a883f5d99cff0c852d3c4 refs/remotes/my-calc-branch03d09b0e2aad427e34a6d50ff147128e76c0e0f5 refs/remotes/tags/2.0.250d02cc0adc9da4319eeba0900430ba219b9c376 refs/remotes/tags/release-2.0.14caaa711a50c77879a91b8b90380060f672745cb refs/remotes/tags/release-2.0.21c4cb508144c513ff1214c3488abe66dcb92916f refs/remotes/tags/release-2.0.2rc11cbd4904d9982f386d87f88fce1c24ad7c0f0471 refs/remotes/trunk而普通的 Git 仓库应该是这个模样:

$ git show-ref83e38c7a0af325a9722f2fdc56b10188806d83a1 refs/heads/master3e15e38c198baac84223acfc6224bb8b99ff2281 refs/remotes/gitserver/master0a30dd3b0c795b80212ae723640d4e5d48cabdff refs/remotes/origin/master25812380387fdd55f916652be4881c6f11600d6f refs/remotes/origin/testing这里有两个远程服务器:一个名为 gitserver ,具有一个 master分支;另一个叫 origin,具有 master 和 testing 两个分支。

注意本例中通过 git svn 导入的远程引用,(Subversion 的)标签是当作远程分支添加的,而不是真正的 Git 标签。导入的 Subversion 仓库仿佛是有一个带有不同分支的 tags 远程服务器。

提交到 Subversion
有了可以开展工作的(本地)仓库以后,你可以开始对该项目做出贡献并向上游仓库提交内容了,Git 这时相当于一个 SVN 客户端。假如编辑了一个文件并进行提交,那么这次提交仅存在于本地的 Git 而非 Subversion 服务器上。

$ git commit -am 'Adding git-svn instructions to the README'[master 97031e5] Adding git-svn instructions to the README 1 files changed, 1 insertions(+), 1 deletions(-)接下来,可以将作出的修改推送到上游。值得注意的是,Subversion 的使用流程也因此改变了——你可以在离线状态下进行多次提交然后一次性的推送到 Subversion 的服务器上。向 Subversion 服务器推送的命令是git svn dcommit:

$ git svn dcommitCommitting to file:///tmp/test-svn/trunk ...       M      README.txtCommitted r79       M      README.txtr79 = 938b1a547c2cc92033b74d32030e86468294a5c8 (trunk)No changes between current HEAD and refs/remotes/trunkResetting to the latest refs/remotes/trunk所有在原 Subversion 数据基础上提交的 commit 会一一提交到 Subversion,然后你本地 Git 的 commit 将被重写,加入一个特别标识。这一步很重要,因为它意味着所有 commit 的 SHA-1 指都会发生变化。这也是同时使用 Git 和 Subversion 两种服务作为远程服务不是个好主意的原因之一。检视以下最后一个 commit,你会找到新添加的git-svn-id (译注:即本段开头所说的特别标识):

$ git log -1commit 938b1a547c2cc92033b74d32030e86468294a5c8Author: schacon <schacon@4c93b258-373f-11de-be05-5f7a86268029>Date:   Sat May 2 22:06:44 2009 +0000    Adding git-svn instructions to the README    git-svn-id: file:///tmp/test-svn/trunk@79 4c93b258-373f-11de-be05-5f7a86268029注意看,原本以 97031e5 开头的 SHA-1 校验值在提交完成以后变成了 938b1a5 。如果既要向 Git 远程服务器推送内容,又要推送到 Subversion 远程服务器,则必须先向 Subversion 推送(dcommit),因为该操作会改变所提交的数据内容。

拉取最新进展
如果要与其他开发者协作,总有那么一天你推送完毕之后,其他人发现他们推送自己修改的时候(与你推送的内容)产生冲突。这些修改在你合并之前将一直被拒绝。在 git svn 里这种情况形似:

$ git svn dcommitCommitting to file:///tmp/test-svn/trunk ...Merge conflict during commit: Your file or directory 'README.txt' is probably \out-of-date: resource out of date; try updating at /Users/schacon/libexec/git-\core/git-svn line 482为了解决该问题,可以运行 git svn rebase ,它会拉取服务器上所有最新的改变,再次基础上衍合你的修改:

$ git svn rebase       M      README.txtr80 = ff829ab914e8775c7c025d741beb3d523ee30bc4 (trunk)First, rewinding head to replay your work on top of it...Applying: first user change现在,你做出的修改都发生在服务器内容之后,所以可以顺利的运行 dcommit :

$ git svn dcommitCommitting to file:///tmp/test-svn/trunk ...       M      README.txtCommitted r81       M      README.txtr81 = 456cbe6337abe49154db70106d1836bc1332deed (trunk)No changes between current HEAD and refs/remotes/trunkResetting to the latest refs/remotes/trunk需要牢记的一点是,Git 要求我们在推送之前先合并上游仓库中最新的内容,而 git svn 只要求存在冲突的时候才这样做。假如有人向一个文件推送了一些修改,这时你要向另一个文件推送一些修改,那么dcommit 将正常工作:

$ git svn dcommitCommitting to file:///tmp/test-svn/trunk ...       M      configure.acCommitted r84       M      autogen.shr83 = 8aa54a74d452f82eee10076ab2584c1fc424853b (trunk)       M      configure.acr84 = cdbac939211ccb18aa744e581e46563af5d962d0 (trunk)W: d2f23b80f67aaaa1f6f5aaef48fce3263ac71a92 and refs/remotes/trunk differ, \  using rebase::100755 100755 efa5a59965fbbb5b2b0a12890f1b351bb5493c18 \  015e4c98c482f0fa71e4d5434338014530b37fa6 M   autogen.shFirst, rewinding head to replay your work on top of it...Nothing to do.这一点需要牢记,因为它的结果是推送之后项目处于一个不完整存在与任何主机上的状态。如果做出的修改无法兼容但没有产生冲突,则可能造成一些很难确诊的难题。这和使用 Git 服务器是不同的——在 Git 世界里,发布之前,你可以在客户端系统里完整的测试项目的状态,而在 SVN 永远都没法确保提交前后项目的状态完全一样。

及时还没打算进行提交,你也应该用这个命令从 Subversion 服务器拉取最新修改。sit svn fetch 能获取最新的数据,不过git svn rebase 才会在获取之后在本地进行更新 。

$ git svn rebase       M      generate_descriptor_proto.shr82 = bd16df9173e424c6f52c337ab6efa7f7643282f1 (trunk)First, rewinding head to replay your work on top of it...Fast-forwarded master to refs/remotes/trunk.不时地运行一下 git svn rebase 可以确保你的代码没有过时。不过,运行该命令时需要确保工作目录的整洁。如果在本地做了修改,则必须在运行git svn rebase 之前或暂存工作,或暂时提交内容——否则,该命令会发现衍合的结果包含着冲突因而终止。

作者: compare2000    时间: 2014-05-22 17:56
Git 分支问题
习惯了 Git 的工作流程以后,你可能会创建一些特性分支,完成相关的开发工作,然后合并他们。如果要用 git svn 向 Subversion 推送内容,那么最好是每次用衍 合来并入一个单一分支,而不是直接合并。使用衍合的原因是 Subversion 只有一个线性的历史而不像 Git 那样处理合并,所以 Git svn 在把快照转换为 Subversion 的 commit 时只能包含第一个祖先。

假设分支历史如下:创建一个 experiment 分支,进行两次提交,然后合并到 master 。在 dcommit 的时候会得到如下输出:

$ git svn dcommitCommitting to file:///tmp/test-svn/trunk ...       M      CHANGES.txtCommitted r85       M      CHANGES.txtr85 = 4bfebeec434d156c36f2bcd18f4e3d97dc3269a2 (trunk)No changes between current HEAD and refs/remotes/trunkResetting to the latest refs/remotes/trunkCOPYING.txt: locally modifiedINSTALL.txt: locally modified       M      COPYING.txt       M      INSTALL.txtCommitted r86       M      INSTALL.txt       M      COPYING.txtr86 = 2647f6b86ccfcaad4ec58c520e369ec81f7c283c (trunk)No changes between current HEAD and refs/remotes/trunkResetting to the latest refs/remotes/trunk在一个包含了合并历史的分支上使用 dcommit 可以成功运行,不过在 Git 项目的历史中,它没有重写你在 experiment 分支中的两个 commit ——另一方面,这些改变却出现在了 SVN 版本中同一个合并 commit 中。

在别人克 隆该项目的时候,只能看到这个合并 commit 包含了所有发生过的修改;他们无法获知修改的作者和时间等提交信息。

Subversion 分支
Subversion 的分支和 Git 中的不尽相同;避免过多的使用可能是最好方案。不过,用 git svn 创建和提交不同的 Subversion 分支仍是可行的。

创建新的 SVN 分支
要在 Subversion 中建立一个新分支,需要运行 git svn branch [分支名] To create a new branch in Subversion, you rungit svn branch [branchname]:

$ git svn branch operaCopying file:///tmp/test-svn/trunk at r87 to file:///tmp/test-svn/branches/opera...Found possible branch point: file:///tmp/test-svn/trunk => \  file:///tmp/test-svn/branches/opera, 87Found branch parent: (opera) 1f6bfe471083cbca06ac8d4176f7ad4de0d62e5fFollowing parent with do_switchSuccessfully followed parentr89 = 9b6fe0b90c5c9adf9165f700897518dbc54a7cbf (opera)相当于在 Subversion 中的 svn copy trunk branches/opera 命令并且对 Subversion 服务器进行了相关操作。值得提醒的是它没有检出和转换到那个分支;如果现在进行提交,将提交到服务器上的trunk, 而非 opera。

切换当前分支
Git 通过搜寻提交历史中 Subversion 分支的头部来决定 dcommit 的目的地——而它应该只有一个,那就是当前分支历史中最近一次包含 git-svn-id 的提交。

如果需要同时在多个分支上提交,可以通过导入 Subversion 上某个其他分支的 commit 来建立以该分支为 dcommit 目的地的本地分支。比如你想拥有一个并行维护的opera 分支,可以运行

$ git branch opera remotes/opera然后,如果要把 opera 分支并入 trunk (本地的 master 分支),可以使用普通的git merge。不过最好提供一条描述提交的信息(通过 -m),否则这次合并的记录是 Merge branch opera ,而不是任何有用的东西。

记住,虽然使用了 git merge 来进行这次操作,并且合并过程可能比使用 Subversion 简单一些(因为 Git 会自动找到适合的合并基础),这并不是一次普通的 Git 合并提交。最终它将被推送回 commit 无法包含多个祖先的 Subversion 服务器上;因而在推送之后,它将变成一个包含了所有在其他分支上做出的改变的单一 commit。把一个分支合并到另一个分支以后,你没法像在 Git 中那样轻易的回到那个分支上继续工作。提交时运行的dcommit 命令擦除了全部有关哪个分支被并入的信息,因而以后的合并基础计算将是不正确的—— dcommit 让 git merge 的结果变得类似于git merge --squash。不幸的是,我们没有什么好办法来避免该情况—— Subversion 无法储存这个信息,所以在使用它作为服务器的时候你将永远为这个缺陷所困。为了不出现这种问题,在把本地分支(本例中的opera)并入 trunk 以后应该立即将其删除。

对应 Subversion 的命令
git svn 工具集合了若干个与 Subversion 类似的功能,对应的命令可以简化向 Git 的转化过程。下面这些命令能实现 Subversion 的这些功能。

SVN 风格的历史
习惯了 Subversion 的人可能想以 SVN 的风格显示历史,运行 git svn log 可以让提交历史显示为 SVN 格式:

$ git svn log------------------------------------------------------------------------r87 | schacon | 2009-05-02 16:07:37 -0700 (Sat, 02 May 2009) | 2 linesautogen change------------------------------------------------------------------------r86 | schacon | 2009-05-02 16:00:21 -0700 (Sat, 02 May 2009) | 2 linesMerge branch 'experiment'------------------------------------------------------------------------r85 | schacon | 2009-05-02 16:00:09 -0700 (Sat, 02 May 2009) | 2 linesupdated the changelog关于 git svn log ,有两点需要注意。首先,它可以离线工作,不像 svn log 命令,需要向 Subversion 服务器索取数据。其次,它仅仅显示已经提交到 Subversion 服务器上的 commit。在本地尚未 dcommit 的 Git 数据不会出现在这里;其他人向 Subversion 服务器新提交的数据也不会显示。等于说是显示了最近已知 Subversion 服务器上的状态。

作者: compare2000    时间: 2014-05-22 17:58
SVN 日志
类似 git svn log 对 git log 的模 拟,svn annotate 的等 效命令是git svn blame [文件名]。其输出如下:

$ git svn blame README.txt  2   temporal Protocol Buffers - Google's data interchange format 2   temporal Copyright 2008 Google Inc. 2   temporal http://code.google.com/apis/protocolbuffers/ 2   temporal 22   temporal C++ Installation - Unix22   temporal ======================= 2   temporal 79    schacon Committing in git-svn.78    schacon  2   temporal To build and install the C++ Protocol Buffer runtime and the Protocol 2   temporal Buffer compiler (protoc) execute the following: 2   temporal 同样,它不显示本地的 Git 提交以及 Subversion 上后来更新的内容。

SVN 服务器信息
还可以使用 git svn info 来获取与运行 svn info 类似的信息:

$ git svn infoPath: .URL: https://schacon-test.googlecode.com/svn/trunkRepository Root: https://schacon-test.googlecode.com/svnRepository UUID: 4c93b258-373f-11de-be05-5f7a86268029Revision: 87Node Kind: directorySchedule: normalLast Changed Author: schaconLast Changed Rev: 87Last Changed Date: 2009-05-02 16:07:37 -0700 (Sat, 02 May 2009)它与 blame 和 log 的相同点在于离线运行以及只更新到最后一次与 Subversion 服务器通信的状态。

略 Subversion 之所略
假如克 隆了一个包含了 svn:ignore 属性的 Subversion 仓库,就有必要建立对应的 .gitignore 文件来防止意外提交一些不 应该提交的文件。git svn 有两个有益于改善该问题的命令。第一个是git svn create-ignore,它自动建立对应的 .gitignore 文件,以便下次提交的时候可以包含它。

第二个命令是 git svn show-ignore,它把需要放进 .gitignore 文件中的内容打印到标准输出,方便我们把输出重定向到项目的黑 名单文件:

$ git svn show-ignore > .git/info/exclude这样一来,避免了 .gitignore 对项目的干扰。如果你是一个 Subversion 团队里唯一的 Git 用户,而其他队友不喜欢项目包含.gitignore,该方法是你的不二之选。

作者: compare2000    时间: 2014-05-22 17:59
本帖最后由 compare2000 于 2014-05-22 18:00 编辑

Git-Svn 总结
git svn 工具集在当前不得不使用 Subversion 服务器或者开发环境要求使用 Subversion 服务器的时候格外有用。不妨把它看成一个跛 脚的 Git,然而,你还是有可能在转换过程中碰到一些困 惑你和合作者们的迷 题。为了避免麻烦,试着遵守如下守则:

Git-Svn 总结
git svn 工具集在当前不得不使用 Subversion 服务器或者开发环境要求使用 Subversion 服务器的时候格外有用。不妨把它看成一个跛 脚的 Git,然而,你还是有可能在转换过程中碰到一些困 惑你和合作者们的迷 题。为了避免麻烦,试着遵守如下守则:

?保持一个不包含由 git merge 生成的 commit 的线性提交历史。将在主线分支外进行的开发 通 通 衍合回主线;避免直接合并。
?不要单独建立和使用一个 Git 服务来搞合 作。可以为了加速新开发者的克 隆进程建立一个,但是不要向它提供任何不包含 git-svn-id 条目的内容。甚至可以添加一个pre-receive 挂 钩来在每一个提交信息中查找 git-svn-id 并拒绝提交那些不包含它的 commit。
如果遵循这些守则,在 Subversion 上工作还可以接受。然而,如果能迁徙到真正的 Git 服务器,则能为团队带来更多好处。



8.2  迁 移到 Git
如果在其他版本控制系统中保存了某项目的代码而后决定转而使用 Git,那么该项目必须经历某种形式的迁移。本节将介绍 Git 中包含的一些针对常见系统的导入脚本,并将展示编写自定义的导入脚本的方法。

导入
你将学习到如何从专业重量级的版本控制系统中导入数据—— Subversion 和 Perforce —— 因为据我所知这二者的用户是(向 Git)转换的主要群体,而且 Git 为此二者附带了高质量的转换工具。

Subversion
读过前一节有关 git svn 的内容以后,你应该能轻而易举的根据其中的指导来 git svn clone 一个仓库了;然后,停止 Subversion 的使用,向一个新 Git server 推送,并开始使用它。想保留历史记录,所花的时间应该不过就是从 Subversion 服务器拉取数据的时间(可能要等上好一会就是了)。

然而,这样的导入并不完美;而且还要花那么多时间,不如干脆一次把它做对!首当其冲的任务是作者信息。在 Subversion,每个提交者在都在主机上有一个用户名,记录在提交信息中。上节例子中多处显示了schacon ,比如 blame 的输出以及 git svn log。如果想让这条信息更好的映射到 Git 作者数据里,则需要 从 Subversion 用户名到 Git 作者的一个映射关系。建立一个叫做user.txt 的文件,用如下格式表示映射关系:

schacon = Scott Chacon <schacon@geemail.com>selse = Someo Nelse <selse@geemail.com>通过该命令可以获得 SVN 作者的列表:

$ svn log --xml | grep author | sort -u | perl -pe 's/.>(.?)<./$1 = /'它将输出 XML 格式的日志——你可以找到作者,建立一个单独的列表,然后从 XML 中抽取出需要的信息。(显而易见,本方法要求主机上安装了grep,sort 和perl.)然后把输出重定向到 user.txt 文件,然后就可以在每一项的后面添加相应的 Git 用户数据。

为 git svn 提供该文件可以然它更精确的映射作者数据。你还可以在 clone 或者 init后面添加--no-metadata 来阻止 git svn 包含那些 Subversion 的附加信息。这样 import 命令就变成了:

$ git-svn clone http://my-project.googlecode.com/svn/ \      --authors-file=users.txt --no-metadata -s my_project现在 my_project 目录下导入的 Subversion 应该比原来整洁多了。原来的 commit 看上去是这样:

commit 37efa680e8473b615de980fa935944215428a35aAuthor: schacon <schacon@4c93b258-373f-11de-be05-5f7a86268029>Date:   Sun May 3 00:12:22 2009 +0000    fixed install - go to trunk    git-svn-id: https://my-project.googlecode.com/svn/trunk@94 4c93b258-373f-11de-    be05-5f7a86268029现在是这样:

commit 03a8785f44c8ea5cdb0e8834b7c8e6c469be2ff2Author: Scott Chacon <schacon@geemail.com>Date:   Sun May 3 00:12:22 2009 +0000    fixed install - go to trunk不仅作者一项干净了不少,git-svn-id 也就此消失了。

你还需要一点 post-import(导入后) 清 理工作。最起码的,应该清理一下 git svn 创建的那些怪 异的索引结构。首先要移动标签,把它们从奇怪的远程分支变成实际的标签,然后把剩下的分支移动到本地。

要把标签变成合适的 Git 标签,运行

$ cp -Rf .git/refs/remotes/tags/* .git/refs/tags/$ rm -Rf .git/refs/remotes/tags该命令将原本以 tag/ 开头的远程分支的索引变成真正的(轻巧的)标签。

接下来,把 refs/remotes 下面剩下的索引变成本地分支:

$ cp -Rf .git/refs/remotes/* .git/refs/heads/$ rm -Rf .git/refs/remotes现在所有的旧分支都变成真正的 Git 分支,所有的旧标签也变成真正的 Git 标签。最后一项工作就是把新建的 Git 服务器添加为远程服务器并且向它推送。下面是新增远程服务器的例子:

$ git remote add origin git@my-git-server:myrepository.git为了让所有的分支和标签都得到上传,我们使用这条命令:

$ git push origin --all所有的分支和标签现在都应该整齐干净的躺在新的 Git 服务器里了。

Perforce
你将了解到的下一个被导入的系统是 Perforce. Git 发行的时候同时也附带了一个 Perforce 导入脚本,不过它是包含在源码的 contrib 部分——而不像git svn 那样默认可用。运行它之前必须获取 Git 的源码,可以在 git.kernel.org 下载:

$ git clone git://git.kernel.org/pub/scm/git/git.git$ cd git/contrib/fast-import在这个 fast-import 目录下,应该有一个叫做 git-p4 的 Python 可执行脚本。主机上必须装有 Python 和p4 工具该导入才能正常进行。例如,你要从 Perforce 公共代码仓库(译注: Perforce Public Depot,Perforce 官方提供的代码寄存服务)导入 Jam 工程。为了设定客户端,我们要把 P4PORT 环境变量 export 到 Perforce 仓库:

$ export P4PORT=public.perforce.com:1666运行 git-p4 clone 命令将从 Perforce 服务器导入 Jam 项目,我们需要给出仓库和项目的路径以及导入的目标路径:

$ git-p4 clone //public/jam/src@all /opt/p4importImporting from //public/jam/src@all into /opt/p4importReinitialized existing Git repository in /opt/p4import/.git/Import destination: refs/remotes/p4/masterImporting revision 4409 (100%)现在去 /opt/p4import 目录运行一下 git log ,就能看到导入的成果:

$ git log -2commit 1fd4ec126171790efd2db83548b85b1bbbc07dc2Author: Perforce staff <support@perforce.com>Date:   Thu Aug 19 10:18:45 2004 -0800    Drop 'rc3' moniker of jam-2.5.  Folded rc2 and rc3 RELNOTES into    the main part of the document.  Built new tar/zip balls.    Only 16 months later.    [git-p4: depot-paths = "//public/jam/src/": change = 4409]commit ca8870db541a23ed867f38847eda65bf4363371dAuthor: Richard Geiger <rmg@perforce.com>Date:   Tue Apr 22 20:51:34 2003 -0800    Update derived jamgram.c    [git-p4: depot-paths = "//public/jam/src/": change = 3108]每一个 commit 里都有一个 git-p4 标识符。这个标识符可以保留,以防以后需要引用 Perforce 的修改版本号。然而,如果想删除这些标识符,现在正是时候——在开启新仓库之前。可以通过git filter-branch 来批量删除这些标识符:

$ git filter-branch --msg-filter '        sed -e "/^\[git-p4:/d"'Rewrite 1fd4ec126171790efd2db83548b85b1bbbc07dc2 (123/123)Ref 'refs/heads/master' was rewritten现在运行一下 git log,你会发现这些 commit 的 SHA-1 校验值都发生了改变,而那些 git-p4 字串则从提交信息里消失了:

$ git log -2commit 10a16d60cffca14d454a15c6164378f4082bc5b0Author: Perforce staff <support@perforce.com>Date:   Thu Aug 19 10:18:45 2004 -0800    Drop 'rc3' moniker of jam-2.5.  Folded rc2 and rc3 RELNOTES into    the main part of the document.  Built new tar/zip balls.    Only 16 months later.commit 2b6c6db311dd76c34c66ec1c40a49405e6b527b2Author: Richard Geiger <rmg@perforce.com>Date:   Tue Apr 22 20:51:34 2003 -0800    Update derived jamgram.c至此导入已经完成,可以开始向新的 Git 服务器推送了。


作者: compare2000    时间: 2014-05-22 18:02
自定导入脚本
如果先前的系统不是 Subversion 或 Perforce 之一,先上网找一下有没有与之对应的导入脚本——导入 CVS,Clear Case,Visual Source Safe,甚至存档目录的导入脚本已经存在。假如这些工具都不适用,或者使用的工具很少见,抑或你需要导入过程具有更多可制定性,则应该使用git fast-import。该命令从标准输入读取简单的指令来写入具体的 Git 数据。这样创建 Git 对象比运行纯 Git 命令或者手动写对象要简单的多(更多相关内容见第九章)。通过它,你可以编写一个导入脚本来从导入源读取必要的信息,同时在标准输出直接输出相关指示。你可以运行该脚本并把它的输出管道连接到git fast-import。

下面演示一下如何编写一个简单的导入脚本。假设你在进行一项工作,并且按时通过把工作目录复制为以时间戳 back_YY_MM_DD 命名的目录来进行备份,现在你需要把它们导入 Git 。目录结构如下:

$ ls /opt/import_fromback_2009_01_02back_2009_01_04back_2009_01_14back_2009_02_03current为了导入到一个 Git 目录,我们首先回顾一下 Git 储存数据的方式。你可能还记得,Git 本质上是一个 commit 对象的链表,每一个对象指向一个内容的快照。而这里需要做的工作就是告诉fast-import 内容快照的位置,什么样的 commit 数据指向它们,以及它们的顺序。我们采取一次处理一个快照的策略,为每一个内容目录建立对应的 commit ,每一个 commit 与之前的建立链接。

正如在第七章 “Git 执行策略一例” 一节中一样,我们将使用 Ruby 来编写这个脚本,因为它是我日常使用的语言而且阅读起来简单一些。你可以用任何其他熟悉的语言来重写这个例子——它仅需要把必要的信息打印到标准输出而已。同时,如果你在使用 Windows,这意味着你要特别留意不要在换行的时候引入回车符(译注:carriage returns,Windows 换行时加入的符号,通常说的\r )—— Git 的 fast-import 对仅使用换行符(LF)而非 Windows 的回车符(CRLF)要求非常严格。

首先,进入目标目录并且找到所有子目录,每一个子目录将作为一个快照被导入为一个 commit。我们将依次进入每一个子目录并打印所需的命令来导出它们。脚本的主循环大致是这样:

last_mark = nil# 循环遍历所有目录Dir.chdir(ARGV[0]) do  Dir.glob("*").each do |dir|    next if File.file?(dir)    # 进入目标目录    Dir.chdir(dir) do       last_mark = print_export(dir, last_mark)    end  endend我们在每一个目录里运行 print_export ,它会取出上一个快照的索引和标记并返回本次快照的索引和标记;由此我们就可以正确的把二者连接起来。”标记(mark)” 是fast-import 中对 commit 标识符的叫法;在创建 commit 的同时,我们逐一赋予一个标记以便以后在把它连接到其他 commit 时使用。因此,在print_export 方法中要做的第一件事就是根据目录名生成一个标记:

mark = convert_dir_to_mark(dir)实现该函数的方法是建立一个目录的数组序列并使用数组的索引值作为标记,因为标记必须是一个整数。这个方法大致是这样的:

$marks = []def convert_dir_to_mark(dir)  if !$marks.include?(dir)    $marks << dir  end  ($marks.index(dir) + 1).to_send有了整数来代表每个 commit,我们现在需要提交附加信息中的日期。由于日期是用目录名表示的,我们就从中解析出来。print_export 文件的下一行将是:

date = convert_dir_to_date(dir)而 convert_dir_to_date 则定义为

def convert_dir_to_date(dir)  if dir == 'current'    return Time.now().to_i  else    dir = dir.gsub('back_', '')    (year, month, day) = dir.split('_')    return Time.local(year, month, day).to_i  endend它为每个目录返回一个整型值。提交附加信息里最后一项所需的是提交者数据,我们在一个全局变量中直接定义之:

$author = 'Scott Chacon <schacon@example.com>'我们差不多可以开始为导入脚本输出提交数据了。第一项信息指明我们定义的是一个 commit 对象以及它所在的分支,随后是我们生成的标记,提交者信息以及提交备注,然后是前一个 commit 的索引,如果有的话。代码大致这样:

# 打印导入所需的信息puts 'commit refs/heads/master'puts 'mark :' + markputs "committer #{$author} #{date} -0700"export_data('imported from ' + dir)puts 'from :' + last_mark if last_mark时区(-0700)处于简化目的使用硬编码。如果是从其他版本控制系统导入,则必须以变量的形式指明时区。 提交备注必须以特定格式给出:

data (size)\n(contents)该格式包含了单词 data,所读取数据的大小,一个换行符,最后是数据本身。由于随后指明文件内容的时候要用到相同的格式,我们写一个辅助方法,export_data:

def export_data(string)  print "data #{string.size}\n#{string}"end唯一剩下的就是每一个快照的内容了。这简单的很,因为它们分别处于一个目录——你可以输出 deleeall 命令,随后是目录中每个文件的内容。Git 会正确的记录每一个快照:

puts 'deleteall'Dir.glob("**/*").each do |file| next if !File.file?(file)  inline_data(file)end注意:由于很多系统把每次修订看作一个 commit 到另一个 commit 的变化量,fast-import 也可以依据每次提交获取一个命令来指出哪些文件被添加,删除或者修改过,以及修改的内容。我们将需要计算快照之间的差别并且仅仅给出这项数据,不过该做法要复杂很多——还如不直接把所有数据丢给 Git 然它自己搞清楚。假如前面这个方法更适用于你的数据,参考fast-import 的 man 帮助页面来了解如何以这种方式提供数据。

列举新文件内容或者指明带有新内容的已修改文件的格式如下:

M 644 inline path/to/filedata (size)(file contents)这里,644 是权限模式(加入有可执行文件,则需要探测之并设定为 755),而 inline 说明我们在本行结束之后立即列出文件的内容。我们的 inline_data 方法大致是:

def inline_data(file, code = 'M', mode = '644')  content = File.read(file)  puts "#{code} #{mode} inline #{file}"  export_data(content)end我们重用了前面定义过的 export_data,因为这里和指明提交注释的格式如出一辙。

最后一项工作是返回当前的标记以便下次循环的使用。

return mark注意:如果你在用 Windows,一定记得添加一项额外的步骤。前面提过,Windows 使用 CRLF 作为换行字符而 Git fast-import 只接受 LF。为了绕开这个问题来满足 git fast-import,你需要让 ruby 用 LF 取代 CRLF:

$stdout.binmode搞定了。现在运行该脚本,你将得到如下内容:

$ ruby import.rb /opt/import_from commit refs/heads/mastermark :1committer Scott Chacon <schacon@geemail.com> 1230883200 -0700data 29imported from back_2009_01_02deleteallM 644 inline file.rbdata 12version twocommit refs/heads/mastermark :2committer Scott Chacon <schacon@geemail.com> 1231056000 -0700data 29imported from back_2009_01_04from :1deleteallM 644 inline file.rbdata 14version threeM 644 inline new.rbdata 16new version one(...)要运行导入脚本,在需要导入的目录把该内容用管道定向到 git fast-import。你可以建立一个空目录然后运行 git init 作为开头,然后运行该脚本:

$ git initInitialized empty Git repository in /opt/import_to/.git/$ ruby import.rb /opt/import_from | git fast-importgit-fast-import statistics:---------------------------------------------------------------------Alloc'd objects:       5000Total objects:           18 (         1 duplicates                  )      blobs  :            7 (         1 duplicates          0 deltas)      trees  :            6 (         0 duplicates          1 deltas)      commits:            5 (         0 duplicates          0 deltas)      tags   :            0 (         0 duplicates          0 deltas)Total branches:           1 (         1 loads     )      marks:           1024 (         5 unique    )      atoms:              3Memory total:          2255 KiB       pools:          2098 KiB     objects:           156 KiB---------------------------------------------------------------------pack_report: getpagesize()            =       4096pack_report: core.packedGitWindowSize =   33554432pack_report: core.packedGitLimit      =  268435456pack_report: pack_used_ctr            =          9pack_report: pack_mmap_calls          =          5pack_report: pack_open_windows        =          1 /          1pack_report: pack_mapped              =       1356 /       1356---------------------------------------------------------------------你会发现,在它成功执行完毕以后,会给出一堆有关已完成工作的数据。上例在一个分支导入了5次提交数据,包含了18个对象。现在可以运行 git log 来检视新的历史:

$ git log -2commit 10bfe7d22ce15ee25b60a824c8982157ca593d41Author: Scott Chacon <schacon@example.com>Date:   Sun May 3 12:57:39 2009 -0700    imported from currentcommit 7e519590de754d079dd73b44d695a42c9d2df452Author: Scott Chacon <schacon@example.com>Date:   Tue Feb 3 01:00:00 2009 -0700    imported from back_2009_02_03就它了——一个干净整洁的 Git 仓库。需要注意的是此时没有任何内容被检出——刚开始当前目录里没有任何文件。要获取它们,你得转到 master 分支的所在:

$ ls$ git reset --hard masterHEAD is now at 10bfe7d imported from current$ lsfile.rb  libfast-import 还可以做更多——处理不同的文件模式,二进制文件,多重分支与合并,标签,进展标识等等。一些更加复杂的实例可以在 Git 源码的contib/fast-import 目录里找到;其中较为出众的是前面提过的 git-p4 脚本。

8.3  总结
现在的你应该掌握了在 Subversion 上使用 Git 以及把几乎任何先存仓库无损失的导入为 Git 仓库。下一章将介绍 Git 内部的原始数据格式,从而是使你能亲手锻造其中的每一个字节,如果必要的话。

作者: compare2000    时间: 2014-05-22 18:03
Git详解九:Git内部原理  
Git 内部原理
不管你是从前面的章节直接跳到了本章,还是读完了其余各章一直到这,你都将在本章见识 Git 的内部工作原理和实现方式。我个人发现学习这些内容对于理解 Git 的用处和强大是非常重要的,不过也有人认为这些内容对于初学者来说可能难以理解且过于复杂。正因如此我把这部分内容放在最后一章,你在学习过程中可以先阅 读这部分,也可以晚点阅读这部分,这完全取决于你自己。

既然已经读到这了,就让我们开始吧。首先要弄明白一点,从根本上来讲 Git 是一套内容寻址 (content-addressable) 文件系统,在此之上提供了一个 VCS 用户界面。马上你就会学到这意味着什么。

早期的 Git (主要是 1.5 之前版本) 的用户界面要比现在复杂得多,这是因为它更侧重于成为文件系统而不是一套更精致的 VCS 。最近几年改进了 UI 从而使它跟其他任何系统一样清晰易用。即便如此,还是经常会有一些陈腔滥调提到早期 Git 的 UI 复杂又难学。

内容寻址文件系统这一层相当酷,在本章中我会先讲解这部分。随后你会学到传输机制和最终要使用的各种库管理任务。



9.1  底层命令 (Plumbing) 和高层命令 (Porcelain)
本书讲解了使用 checkout, branch, remote 等共约 30 个 Git 命令。然而由于 Git 一开始被设计成供 VCS 使用的工具集而不是一整套用户友好的 VCS,它还包含了许多底层命令,这些命令用于以 UNIX 风格使用或由脚本调用。这些命令一般被称为 “plumbing” 命令(底层命令),其他的更友好的命令则被称为 “porcelain” 命令(高层命令)。

本书前八章主要专门讨论高层命令。本章将主要讨论底层命令以理解 Git 的内部工作机制、演示 Git 如何及为何要以这种方式工作。这些命令主要不是用来从命令行手工使用的,更多的是用来为其他工具和自定义脚本服务的。

当你在一个新目录或已有目录内执行 git init 时,Git 会创建一个 .git 目录,几乎所有 Git 存储和操作的内容都位于该目录下。如果你要备份或复制一个库,基本上将这一目录拷贝至其他地方就可以了。本章基本上都讨论该目录下的内容。该目录结构如下:

$ lsHEADbranches/configdescriptionhooks/indexinfo/objects/refs/该目录下有可能还有其他文件,但这是一个全新的 git init 生成的库,所以默认情况下这些就是你能看到的结构。新版本的 Git 不再使用branches 目录,description 文件仅供 GitWeb 程序使用,所以不用关心这些内容。config 文件包含了项目特有的配置选项,info 目录保存了一份不希望在 .gitignore 文件中管理的忽略模式 (ignored patterns) 的全局可执行文件。hooks 目录包住了第六章详细介绍了的客户端或服务端钩子脚本。

另外还有四个重要的文件或目录:HEAD 及 index 文件,objects 及refs 目录。这些是 Git 的核心部分。objects 目录存储所有数据内容,refs 目录存储指向数据 (分支) 的提交对象的指针,HEAD 文件指向当前分支,index 文件保存了暂存区域信息。马上你将详细了解 Git 是如何操纵这些内容的。



9.2  Git 对象
Git 是一套内容寻址文件系统。很不错。不过这是什么意思呢?这种说法的意思是,从内部来看,Git 是简单的 key-value 数据存储。它允许插入任意类型的内容,并会返回一个键值,通过该键值可以在任何时候再取出该内容。可以通过底层命令hash-object 来示范这点,传一些数据给该命令,它会将数据保存在 .git 目录并返回表示这些数据的键值。首先初使化一个 Git 仓库并确认objects 目录是空的:

$ mkdir test$ cd test$ git initInitialized empty Git repository in /tmp/test/.git/$ find .git/objects.git/objects.git/objects/info.git/objects/pack$ find .git/objects -type f$Git 初始化了 objects 目录,同时在该目录下创建了 pack 和 info 子目录,但是该目录下没有其他常规文件。我们往这个 Git 数据库里存储一些文本:

$ echo 'test content' | git hash-object -w --stdind670460b4b4aece5915caf5c68d12f560a9fe3e4参数 -w 指示 hash-object 命令存储 (数据) 对象,若不指定这个参数该命令仅仅返回键值。--stdin 指定从标准输入设备 (stdin) 来读取内容,若不指定这个参数则需指定一个要存储的文件的路径。该命令输出长度为 40 个字符的校验和。这是个 SHA-1 哈希值──其值为要存储的数据加上你马上会了解到的一种头信息的校验和。现在可以查看到 Git 已经存储了数据:

$ find .git/objects -type f.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4可以在 objects 目录下看到一个文件。这便是 Git 存储数据内容的方式──为每份内容生成一个文件,取得该内容与头信息的 SHA-1 校验和,创建以该校验和前两个字符为名称的子目录,并以 (校验和) 剩下 38 个字符为文件命名 (保存至子目录下)。

通过 cat-file 命令可以将数据内容取回。该命令是查看 Git 对象的瑞士军刀。传入 -p 参数可以让该命令输出数据内容的类型:

$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4test content可以往 Git 中添加更多内容并取回了。也可以直接添加文件。比方说可以对一个文件进行简单的版本控制。首先,创建一个新文件,并把文件内容存储到数据库中:

$ echo 'version 1' > test.txt$ git hash-object -w test.txt83baae61804e65cc73a7201a7252750c76066a30接着往该文件中写入一些新内容并再次保存:

$ echo 'version 2' > test.txt$ git hash-object -w test.txt1f7a7a472abf3dd9643fd615f6da379c4acb3e3a数据库中已经将文件的两个新版本连同一开始的内容保存下来了:

$ find .git/objects -type f.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a.git/objects/83/baae61804e65cc73a7201a7252750c76066a30.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4再将文件恢复到第一个版本:

$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt$ cat test.txtversion 1或恢复到第二个版本:

$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt$ cat test.txtversion 2需要记住的是几个版本的文件 SHA-1 值可能与实际的值不同,其次,存储的并不是文件名而仅仅是文件内容。这种对象类型称为 blob 。通过传递 SHA-1 值给cat-file -t 命令可以让 Git 返回任何对象的类型:

$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3ablobtree (树) 对象
接下去来看 tree 对象,tree 对象可以存储文件名,同时也允许存储一组文件。Git 以一种类似 UNIX 文件系统但更简单的方式来存储内容。所有内容以 tree 或 blob 对象存储,其中 tree 对象对应于 UNIX 中的目录,blob 对象则大致对应于 inodes 或文件内容。一个单独的 tree 对象包含一条或多条 tree 记录,每一条记录含有一个指向 blob 或子 tree 对象的 SHA-1 指针,并附有该对象的权限模式 (mode)、类型和文件名信息。以 simplegit 项目为例,最新的 tree 可能是这个样子:

$ git cat-file -p master^{tree}100644 blob a906cb2a4a904a152e80877d4088654daad0c859      README100644 blob 8f94139338f9404f26296befa88755fc2598c289      Rakefile040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0      libmaster^{tree} 表示 branch 分支上最新提交指向的 tree 对象。请注意 lib 子目录并非一个 blob 对象,而是一个指向别一个 tree 对象的指针:

$ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b      simplegit.rb从概念上来讲,Git 保存的数据如图 9-1 所示。



图 9-1. Git 对象模型的简化版
你可以自己创建 tree 。通常 Git 根据你的暂存区域或 index 来创建并写入一个 tree 。因此要创建一个 tree 对象的话首先要通过将一些文件暂存从而创建一个 index 。可以使用 plumbing 命令update-index 为一个单独文件 ── test.txt 文件的第一个版本 ──    创建一个 index    。通过该命令人为的将 test.txt 文件的首个版本加入到了一个新的暂存区域中。由于该文件原先并不在暂存区域中 (甚至就连暂存区域也还没被创建出来呢) ,必须传入--add 参数;由于要添加的文件并不在当前目录下而是在数据库中,必须传入 --cacheinfo 参数。同时指定了文件模式,SHA-1 值和文件名:

$ git update-index --add --cacheinfo 100644 \  83baae61804e65cc73a7201a7252750c76066a30 test.txt在本例中,指定了文件模式为 100644,表明这是一个普通文件。其他可用的模式有:100755 表示可执行文件,120000 表示符号链接。文件模式是从常规的 UNIX 文件模式中参考来的,但是没有那么灵活 ── 上述三种模式仅对 Git 中的文件 (blobs) 有效 (虽然也有其他模式用于目录和子模块)。

现在可以用 write-tree 命令将暂存区域的内容写到一个 tree 对象了。无需 -w 参数 ── 如果目标 tree 不存在,调用write-tree 会自动根据 index 状态创建一个 tree 对象。

$ git write-treed8329fc1cc938780ffdd9f94e0d364e0ea74f579$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579100644 blob 83baae61804e65cc73a7201a7252750c76066a30      test.txt可以这样验证这确实是一个 tree 对象:

$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579tree再根据 test.txt 的第二个版本以及一个新文件创建一个新 tree 对象:

$ echo 'new file' > new.txt$ git update-index test.txt$ git update-index --add new.txt这时暂存区域中包含了 test.txt 的新版本及一个新文件 new.txt 。创建 (写) 该 tree 对象 (将暂存区域或 index 状态写入到一个 tree 对象),然后瞧瞧它的样子:

$ git write-tree0155eb4229851634a0f03eb265b69f5a2d56f341$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a      test.txt请注意该 tree 对象包含了两个文件记录,且 test.txt 的 SHA 值是早先值的 “第二版” (1f7a7a)。来点更有趣的,你将把第一个 tree 对象作为一个子目录加进该 tree 中。可以用read-tree 命令将 tree 对象读到暂存区域中去。在这时,通过传一个 --prefix 参数给 read-tree,将一个已有的 tree 对象作为一个子 tree 读到暂存区域中:

$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579$ git write-tree3c4e9cd789d88d8d89c1073707c3585e41b0e614$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579      bak100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a      test.txt如果从刚写入的新 tree 对象创建一个工作目录,将得到位于工作目录顶级的两个文件和一个名为 bak 的子目录,该子目录包含了 test.txt 文件的第一个版本。可以将 Git 用来包含这些内容的数据想象成如图 9-2 所示的样子。



图 9-2. 当前 Git 数据的内容结构
commit (提交) 对象
你现在有三个 tree 对象,它们指向了你要跟踪的项目的不同快照,可是先前的问题依然存在:必须记往三个 SHA-1 值以获得这些快照。你也没有关于谁、何时以及为何保存了这些快照的信息。commit 对象为你保存了这些基本信息。

要创建一个 commit 对象,使用 commit-tree 命令,指定一个 tree 的 SHA-1,如果有任何前继提交对象,也可以指定。从你写的第一个 tree 开始:

$ echo 'first commit' | git commit-tree d8329ffdf4fc3344e67ab068f836878b6c4951e3b15f3d通过 cat-file 查看这个新 commit 对象:

$ git cat-file -p fdf4fc3 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 author Scott Chacon 1243040974 -0700 committer Scott Chacon 1243040974 -0700 first commit  commit 对象有格式很简单:指明了该时间点项目快照的顶层树对象、作者/提交者信息(从 Git 设理发店的 user.name 和user.email中获得)以及当前时间戳、一个空行,以及提交注释信息。

接着再写入另外两个 commit 对象,每一个都指定其之前的那个 commit 对象:

$ echo 'second commit' | git commit-tree 0155eb -p fdf4fc3cac0cab538b970a37ea1e769cbbde608743bc96d$ echo 'third commit'  | git commit-tree 3c4e9c -p cac0cab1a410efbd13591db07496601ebc7a059dd55cfe9每一个 commit 对象都指向了你创建的树对象快照。出乎意料的是,现在已经有了真实的 Git 历史了,所以如果运行 git log 命令并指定最后那个 commit 对象的 SHA-1 便可以查看历史:

$ git log --stat 1a410e commit 1a410efbd13591db07496601ebc7a059dd55cfe9 Author: Scott Chacon Date: Fri May 22 18:15:24 2009 -0700 third commit bak/test.txt | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) commit cac0cab538b970a37ea1e769cbbde608743bc96d Author: Scott Chacon Date: Fri May 22 18:14:29 2009 -0700 second commit new.txt | 1 + test.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletions(-) commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d Author: Scott Chacon Date: Fri May 22 18:09:34 2009 -0700 first commit test.txt | 1 + 1 files changed, 1 insertions(+), 0 deletions(-)   真棒。你刚刚通过使用低级操作而不是那些普通命令创建了一个 Git 历史。这基本上就是运行    git add 和 git commit 命令时 Git 进行的工作    ──保存修改了的文件的 blob,更新索引,创建 tree 对象,最后创建 commit 对象,这些 commit 对象指向了顶层 tree 对象以及先前的 commit 对象。这三类 Git 对象 ── blob,tree 以及 tree ── 都各自以文件的方式保存在.git/objects 目录下。以下所列是目前为止样例中的所有对象,每个对象后面的注释里标明了它们保存的内容:

$ find .git/objects -type f.git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2.git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2.git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1如果你按照以上描述进行了操作,可以得到如图 9-3 所示的对象图。



图 9-3. Git 目录下的所有对象
对象存储
之前我提到当存储数据内容时,同时会有一个文件头被存储起来。我们花些时间来看看 Git 是如何存储对象的。你将看来如何通过 Ruby 脚本语言存储一个 blob 对象 (这里以字符串 “what is up, doc?” 为例) 。使用irb 命令进入 Ruby 交互式模式:

$ irb>> content = "what is up, doc?"=> "what is up, doc?"Git 以对象类型为起始内容构造一个文件头,本例中是一个 blob。然后添加一个空格,接着是数据内容的长度,最后是一个空字节 (null byte):

>> header = "blob #{content.length}\0"=> "blob 16\000"Git 将文件头与原始数据内容拼接起来,并计算拼接后的新内容的 SHA-1 校验和。可以在 Ruby 中使用 require 语句导入 SHA1 digest 库,然后调用Digest::SHA1.hexdigest() 方法计算字符串的 SHA-1 值:

>> store = header + content=> "blob 16\000what is up, doc?">> require 'digest/sha1'=> true>> sha1 = Digest::SHA1.hexdigest(store)=> "bd9dbf5aae1a3862dd1526723246b20206e5fc37"Git 用 zlib 对数据内容进行压缩,在 Ruby 中可以用 zlib 库来实现。首先需要导入该库,然后用 Zlib:eflate.deflate() 对数据进行压缩:

>> require 'zlib'=> true>> zlib_content = Zlib:eflate.deflate(store)=> "x\234K\312\311OR04c(\317H,Q\310,V(-\320QH\311O\266\a\000_\034\a\235"最后将用 zlib 压缩后的内容写入磁盘。需要指定保存对象的路径 (SHA-1 值的头两个字符作为子目录名称,剩余 38 个字符作为文件名保存至该子目录中)。在 Ruby 中,如果子目录不存在可以用FileUtils.mkdir_p() 函数创建它。接着用 File.open 方法打开文件,并用 write() 方法将之前压缩的内容写入该文件:

>> path = '.git/objects/' + sha1[0,2] + '/' + sha1[2,38]=> ".git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37">> require 'fileutils'=> true>> FileUtils.mkdir_p(File.dirname(path))=> ".git/objects/bd">> File.open(path, 'w') { |f| f.write zlib_content }=> 32这就行了 ── 你已经创建了一个正确的 blob 对象。所有的 Git 对象都以这种方式存储,惟一的区别是类型不同 ── 除了字符串 blob,文件头起始内容还可以是 commit 或 tree 。不过虽然 blob 几乎可以是任意内容,commit 和 tree 的数据却是有固定格式的。



9.3  Git References
你可以执行像 git log 1a410e 这样的命令来查看完整的历史,但是这样你就要记得 1a410e 是你最后一次提交,这样才能在提交历史中找到这些对象。你需要一个文件来用一个简单的名字来记录这些 SHA-1 值,这样你就可以用这些指针而不是原来的 SHA-1 值去检索了。

在 Git 中,我们称之为“引用”(references 或者 refs,译者注)。你可以在 .git/refs 目录下面找到这些包含 SHA-1 值的文件。在这个项目里,这个目录还没不包含任何文件,但是包含这样一个简单的结构:

$ find .git/refs.git/refs.git/refs/heads.git/refs/tags$ find .git/refs -type f$如果想要创建一个新的引用帮助你记住最后一次提交,技术上你可以这样做:

$ echo "1a410efbd13591db07496601ebc7a059dd55cfe9" > .git/refs/heads/master现在,你就可以在 Git 命令中使用你刚才创建的引用而不是 SHA-1 值:

$ git log --pretty=oneline  master1a410efbd13591db07496601ebc7a059dd55cfe9 third commitcac0cab538b970a37ea1e769cbbde608743bc96d second commitfdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit当然,我们并不鼓励你直接修改这些引用文件。如果你确实需要更新一个引用,Git 提供了一个安全的命令 update-ref:

$ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9基本上 Git 中的一个分支其实就是一个指向某个工作版本一条 HEAD 记录的指针或引用。你可以用这条命令创建一个指向第二次提交的分支:

$ git update-ref refs/heads/test cac0ca这样你的分支将会只包含那次提交以及之前的工作:

$ git log --pretty=oneline testcac0cab538b970a37ea1e769cbbde608743bc96d second commitfdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit现在,你的 Git 数据库应该看起来像图 9-4 一样。



图 9-4. 包含分支引用的 Git 目录对象
每当你执行 git branch (分支名称) 这样的命令,Git 基本上就是执行 update-ref 命令,把你现在所在分支中最后一次提交的 SHA-1 值,添加到你要创建的分支的引用。

HEAD 标记
现在的问题是,当你执行 git branch (分支名称) 这条命令的时候,Git 怎么知道最后一次提交的 SHA-1 值呢?答案就是 HEAD 文件。HEAD 文件是一个指向你当前所在分支的引用标识符。这样的引用标识符——它看起来并不像一个普通的引用——其实并不包含 SHA-1 值,而是一个指向另外一个引用的指针。如果你看一下这个文件,通常你将会看到这样的内容:

$ cat .git/HEADref: refs/heads/master如果你执行 git checkout test,Git 就会更新这个文件,看起来像这样:

$ cat .git/HEADref: refs/heads/test当你再执行 git commit 命令,它就创建了一个 commit 对象,把这个 commit 对象的父级设置为 HEAD 指向的引用的 SHA-1 值。

你也可以手动编辑这个文件,但是同样有一个更安全的方法可以这样做:symbolic-ref。你可以用下面这条命令读取 HEAD 的值:

$ git symbolic-ref HEADrefs/heads/master你也可以设置 HEAD 的值:

$ git symbolic-ref HEAD refs/heads/test$ cat .git/HEADref: refs/heads/test但是你不能设置成 refs 以外的形式:

$ git symbolic-ref HEAD testfatal: Refusing to point HEAD outside of refs/Tags
你刚刚已经重温过了 Git 的三个主要对象类型,现在这是第四种。Tag 对象非常像一个 commit 对象——包含一个标签,一组数据,一个消息和一个指针。最主要的区别就是 Tag 对象指向一个 commit 而不是一个 tree。它就像是一个分支引用,但是不会变化——永远指向同一个 commit,仅仅是提供一个更加友好的名字。

正如我们在第二章所讨论的,Tag 有两种类型:annotated 和 lightweight 。你可以类似下面这样的命令建立一个 lightweight tag:

$ git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d这就是 lightweight tag 的全部 —— 一个永远不会发生变化的分支。 annotated tag 要更复杂一点。如果你创建一个 annotated tag,Git 会创建一个 tag 对象,然后写入一个指向指向它而不是直接指向 commit 的 reference。你可以这样创建一个 annotated tag(-a 参数表明这是一个 annotated tag):

$ git tag -a v1.1 1a410efbd13591db07496601ebc7a059dd55cfe9 -m 'test tag'这是所创建对象的 SHA-1 值:

$ cat .git/refs/tags/v1.19585191f37f7b0fb9444f35a9bf50de191beadc2现在你可以运行 cat-file 命令检查这个 SHA-1 值:

$ git cat-file -p 9585191f37f7b0fb9444f35a9bf50de191beadc2 object 1a410efbd13591db07496601ebc7a059dd55cfe9 type commit tag v1.1 tagger Scott Chacon Sat May 23 16:48:58 2009 -0700 test tag值得注意的是这个对象指向你所标记的 commit 对象的 SHA-1 值。同时需要注意的是它并不是必须要指向一个 commit 对象;你可以标记任何 Git 对象。例如,在 Git 的源代码里,管理者添加了一个 GPG 公钥(这是一个 blob 对象)对它做了一个标签。你就可以运行:

$ git cat-file blob junio-gpg-pub来查看 Git 源代码仓库中的公钥. Linux kernel 也有一个不是指向 commit 对象的 tag —— 第一个 tag 是在导入源代码的时候创建的,它指向初始 tree (initial tree,译者注)。

Remotes
你将会看到的第四种 reference 是 remote reference(远程引用,译者注)。如果你添加了一个 remote 然后推送代码过去,Git 会把你最后一次推送到这个 remote 的每个分支的值都记录在refs/remotes 目录下。例如,你可以添加一个叫做 origin 的 remote 然后把你的 master 分支推送上去:

$ git remote add origin git@github.com:schacon/simplegit-progit.git$ git push origin masterCounting objects: 11, done.Compressing objects: 100% (5/5), done.Writing objects: 100% (7/7), 716 bytes, done.Total 7 (delta 2), reused 4 (delta 1)To git@github.com:schacon/simplegit-progit.git   a11bef0..ca82a6d  master -> master然后查看 refs/remotes/origin/master 这个文件,你就会发现 origin remote 中的master 分支就是你最后一次和服务器的通信。

$ cat .git/refs/remotes/origin/masterca82a6dff817ec66f44342007202690a93763949Remote 应用和分支主要区别在于他们是不能被 check out 的。Git 把他们当作是标记这些了这些分支在服务器上最后状态的一种书签。



9.4  Packfiles
我们再来看一下 test Git 仓库。目前为止,有 11 个对象 ── 4 个 blob,3 个 tree,3 个 commit 以及一个 tag:

$ find .git/objects -type f.git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2.git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2.git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1.git/objects/95/85191f37f7b0fb9444f35a9bf50de191beadc2 # tag.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1Git 用 zlib 压缩文件内容,因此这些文件并没有占用太多空间,所有文件加起来总共仅用了 925 字节。接下去你会添加一些大文件以演示 Git 的一个很有意思的功能。将你之前用到过的 Grit 库中的 repo.rb 文件加进去 ── 这个源代码文件大小约为 12K:

$ curl http://github.com/mojombo/grit/raw/master/lib/grit/repo.rb > repo.rb$ git add repo.rb$ git commit -m 'added repo.rb'[master 484a592] added repo.rb 3 files changed, 459 insertions(+), 2 deletions(-) delete mode 100644 bak/test.txt create mode 100644 repo.rb rewrite test.txt (100%)如果查看一下生成的 tree,可以看到 repo.rb 文件的 blob 对象的 SHA-1 值:

$ git cat-file -p master^{tree}100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt100644 blob 9bc1dc421dcd51b4ac296e3e5b6e2a99cf44391e      repo.rb100644 blob e3f094f522629ae358806b17daf78246c27c007b      test.txt然后可以用 git cat-file 命令查看这个对象有多大:

$ git cat-file -s 9bc1dc421dcd51b4ac296e3e5b6e2a99cf44391e12898稍微修改一下些文件,看会发生些什么:

$ echo '# testing' >> repo.rb$ git commit -am 'modified repo a bit'[master ab1afef] modified repo a bit 1 files changed, 1 insertions(+), 0 deletions(-)查看这个 commit 生成的 tree,可以看到一些有趣的东西:

$ git cat-file -p master^{tree}100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt100644 blob 05408d195263d853f09dca71d55116663690c27c      repo.rb100644 blob e3f094f522629ae358806b17daf78246c27c007b      test.txtblob 对象与之前的已经不同了。这说明虽然只是往一个 400 行的文件最后加入了一行内容,Git 却用一个全新的对象来保存新的文件内容:

$ git cat-file -s 05408d195263d853f09dca71d55116663690c27c12908你的磁盘上有了两个几乎完全相同的 12K 的对象。如果 Git 只完整保存其中一个,并保存另一个对象的差异内容,岂不更好?

事实上 Git 可以那样做。Git 往磁盘保存对象时默认使用的格式叫松散对象 (loose object) 格式。Git 时不时地将这些对象打包至一个叫 packfile 的二进制文件以节省空间并提高效率。当仓库中有太多的松散对象,或是手工调用git gc 命令,或推送至远程服务器时,Git 都会这样做。手工调用 git gc 命令让 Git 将库中对象打包并看会发生些什么:

$ git gcCounting objects: 17, done.Delta compression using 2 threads.Compressing objects: 100% (13/13), done.Writing objects: 100% (17/17), done.Total 17 (delta 1), reused 10 (delta 0)查看一下 objects 目录,会发现大部分对象都不在了,与此同时出现了两个新文件:

$ find .git/objects -type f.git/objects/71/08f7ecb345ee9d0084193f147cdad4d2998293.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4.git/objects/info/packs.git/objects/pack/pack-7a16e4488ae40c7d2bc56ea2bd43e25212a66c45.idx.git/objects/pack/pack-7a16e4488ae40c7d2bc56ea2bd43e25212a66c45.pack仍保留着的几个对象是未被任何 commit 引用的 blob ── 在此例中是你之前创建的 “what is up, doc?” 和 “test content” 这两个示例 blob。你从没将他们添加至任何 commit,所以 Git 认为它们是 “悬空” 的,不会将它们打包进 packfile 。

剩下的文件是新创建的 packfile 以及一个索引。packfile 文件包含了刚才从文件系统中移除的所有对象。索引文件包含了 packfile 的偏移信息,这样就可以快速定位任意一个指定对象。有意思的是运行gc 命令前磁盘上的对象大小约为 12K ,而这个新生成的 packfile 仅为 6K 大小。通过打包对象减少了一半磁盘使用空间。

Git 是如何做到这点的?Git 打包对象时,会查找命名及尺寸相近的文件,并只保存文件不同版本之间的差异内容。可以查看一下 packfile ,观察它是如何节省空间的。git verify-pack 命令用于显示已打包的内容:

$ git verify-pack -v \  .git/objects/pack/pack-7a16e4488ae40c7d2bc56ea2bd43e25212a66c45.idx0155eb4229851634a0f03eb265b69f5a2d56f341 tree   71 76 540005408d195263d853f09dca71d55116663690c27c blob   12908 3478 87409f01cea547666f58d6a8d809583841a7c6f0130 tree   106 107 50861a410efbd13591db07496601ebc7a059dd55cfe9 commit 225 151 3221f7a7a472abf3dd9643fd615f6da379c4acb3e3a blob   10 19 53813c4e9cd789d88d8d89c1073707c3585e41b0e614 tree   101 105 5211484a59275031909e19aadb7c92262719cfcdf19a commit 226 153 16983baae61804e65cc73a7201a7252750c76066a30 blob   10 19 53629585191f37f7b0fb9444f35a9bf50de191beadc2 tag    136 127 54769bc1dc421dcd51b4ac296e3e5b6e2a99cf44391e blob   7 18 5193 105408d195263d853f09dca71d55116663690c27c \  ab1afef80fac8e34258ff41fc1b867c702daa24b commit 232 157 12cac0cab538b970a37ea1e769cbbde608743bc96d commit 226 154 473d8329fc1cc938780ffdd9f94e0d364e0ea74f579 tree   36 46 5316e3f094f522629ae358806b17daf78246c27c007b blob   1486 734 4352f8f51d7d8a1760462eca26eebafde32087499533 tree   106 107 749fa49b077972391ad58037050f2a75f74e3671e92 blob   9 18 856fdf4fc3344e67ab068f836878b6c4951e3b15f3d commit 177 122 627chain length = 1: 1 objectpack-7a16e4488ae40c7d2bc56ea2bd43e25212a66c45.pack: ok如果你还记得的话, 9bc1d 这个 blob 是 repo.rb 文件的第一个版本,这个 blob 引用了 05408 这个 blob,即该文件的第二个版本。命令输出内容的第三列显示的是对象大小,可以看到05408 占用了 12K 空间,而 9bc1d 仅为 7 字节。非常有趣的是第二个版本才是完整保存文件内容的对象,而第一个版本是以差异方式保存的 ── 这是因为大部分情况下需要快速访问文件的最新版本。

最妙的是可以随时进行重新打包。Git 自动定期对仓库进行重新打包以节省空间。当然也可以手工运行 git gc 命令来这么做。



9.5  The Refspec
这本书读到这里,你已经使用过一些简单的远程分支到本地引用的映射方式了,这种映射可以更为复杂。 假设你像这样添加了一项远程仓库:

$ git remote add origin git@github.com:schacon/simplegit-progit.git它在你的 .git/config 文件中添加了一节,指定了远程的名称 (origin), 远程仓库的URL地址,和用于获取操作的 Refspec:

[remote "origin"]       url = git@github.com:schacon/simplegit-progit.git       fetch = +refs/heads/*:refs/remotes/origin/*Refspec 的格式是一个可选的 + 号,接着是 : 的格式,这里 是远端上的引用格式, 是将要记录在本地的引用格式。可选的 + 号告诉 Git 在即使不能快速演进的情况下,也去强制更新它。

缺省情况下 refspec 会被 git remote add 命令所自动生成, Git 会获取远端上 refs/heads/ 下面的所有引用,并将它写入到本地的refs/remotes/origin/. 所以,如果远端上有一个 master 分支,你在本地可以通过下面这种方式来访问它的历史记录:

$ git log origin/master$ git log remotes/origin/master$ git log refs/remotes/origin/master它们全是等价的,因为 Git 把它们都扩展成 refs/remotes/origin/master.

如果你想让 Git 每次只拉取远程的 master 分支,而不是远程的所有分支,你可以把 fetch 这一行修改成这样:

fetch = +refs/heads/master:refs/remotes/origin/master这是 git fetch 操作对这个远端的缺省 refspec 值。而如果你只想做一次该操作,也可以在命令行上指定这个 refspec. 如可以这样拉取远程的master 分支到本地的 origin/mymaster 分支:

$ git fetch origin master:refs/remotes/origin/mymaster你也可以在命令行上指定多个 refspec. 像这样可以一次获取远程的多个分支:

$ git fetch origin master:refs/remotes/origin/mymaster \   topic:refs/remotes/origin/topicFrom git@github.com:schacon/simplegit ! [rejected]        master     -> origin/mymaster  (non fast forward) * [new branch]      topic      -> origin/topic在这个例子中, master 分支因为不是一个可以快速演进的引用而拉取操作被拒绝。你可以在 refspec 之前使用一个 + 号来重载这种行为。

你也可以在配置文件中指定多个 refspec. 如你想在每次获取时都获取 master 和 experiment 分支,就添加两行:

[remote "origin"]       url = git@github.com:schacon/simplegit-progit.git       fetch = +refs/heads/master:refs/remotes/origin/master       fetch = +refs/heads/experiment:refs/remotes/origin/experiment但是这里不能使用部分通配符,像这样就是不合法的:

fetch = +refs/heads/qa*:refs/remotes/origin/qa*但无论如何,你可以使用命名空间来达到这个目的。如你有一个QA组,他们推送一系列分支,你想每次获取 master 分支和QA组的所有分支,你可以使用这样的配置段落:

[remote "origin"]       url = git@github.com:schacon/simplegit-progit.git       fetch = +refs/heads/master:refs/remotes/origin/master       fetch = +refs/heads/qa/*:refs/remotes/origin/qa/*如果你的工作流很复杂,有QA组推送的分支、开发人员推送的分支、和集成人员推送的分支,并且他们在远程分支上协作,你可以采用这种方式为他们创建各自的命名空间。

推送 Refspec
采用命名空间的方式确实很棒,但QA组成员第1次是如何将他们的分支推送到 qa/ 空间里面的呢?答案是你可以使用 refspec 来推送。

如果QA组成员想把他们的 master 分支推送到远程的 qa/master 分支上,可以这样运行:

$ git push origin master:refs/heads/qa/master如果他们想让 Git 每次运行 git push origin 时都这样自动推送,他们可以在配置文件中添加 push 值:

[remote "origin"]       url = git@github.com:schacon/simplegit-progit.git       fetch = +refs/heads/*:refs/remotes/origin/*       push = refs/heads/master:refs/heads/qa/master这样,就会让 git push origin 缺省就把本地的 master 分支推送到远程的 qa/master 分支上。

删除引用
你也可以使用 refspec 来删除远程的引用,是通过运行这样的命令:

$ git push origin :topic因为 refspec 的格式是 : , 通过把 部分留空的方式,这个意思是是把远程的topic 分支变成空,也就是删除它。



9.6  传输协议
Git 可以以两种主要的方式跨越两个仓库传输数据:基于HTTP协议之上,和 file://, ssh://, 和git:// 等智能传输协议。这一节带你快速浏览这两种主要的协议操作过程。

哑协议
Git 基于HTTP之上传输通常被称为哑协议,这是因为它在服务端不需要有针对 Git 特有的代码。这个获取过程仅仅是一系列GET请求,客户端可以假定服务端的Git仓库中的布局。让我们以 simplegit 库来看看http-fetch 的过程:

$ git clone http://github.com/schacon/simplegit-progit.git它做的第1件事情就是获取 info/refs 文件。这个文件是在服务端运行了 update-server-info 所生成的,这也解释了为什么在服务端要想使用HTTP传输,必须要开启post-receive 钩子:

=> GET info/refsca82a6dff817ec66f44342007202690a93763949     refs/heads/master现在你有一个远端引用和SHA值的列表。下一步是寻找HEAD引用,这样你就知道了在完成后,什么应该被检出到工作目录:

=> GET HEADref: refs/heads/master这说明在完成获取后,需要检出 master 分支。 这时,已经可以开始漫游操作了。因为你的起点是在 info/refs 文件中所提到的ca82a6 commit 对象,你的开始操作就是获取它:

=> GET objects/ca/82a6dff817ec66f44342007202690a93763949(179 bytes of binary data)然后你取回了这个对象 - 这在服务端是一个松散格式的对象,你使用的是静态的 HTTP GET 请求获取的。可以使用 zlib 解压缩它,去除其头部,查看它的 commmit 内容:

$ git cat-file -p ca82a6dff817ec66f44342007202690a93763949 tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 author Scott Chacon 1205815931 -0700 committer Scott Chacon 1240030591 -0700 changed the version number  这样,就得到了两个需要进一步获取的对象 - cfda3b 是这个 commit 对象所对应的 tree 对象,和 085bb3 是它的父对象;

=> GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7(179 bytes of data)这样就取得了这它的下一步 commit 对象,再抓取 tree 对象:

=> GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf(404 - Not Found)Oops - 看起来这个 tree 对象在服务端并不以松散格式对象存在,所以得到了404响应,代表在HTTP服务端没有找到该对象。这有好几个原因 - 这个对象可能在替代仓库里面,或者在打包文件里面, Git 会首先检查任何列出的替代仓库:

=> GET objects/info/http-alternates(empty file)如果这返回了几个替代仓库列表,那么它会去那些地方检查松散格式对象和文件 - 这是一种在软件分叉之间共享对象以节省磁盘的好方法。然而,在这个例子中,没有替代仓库。所以你所需要的对象肯定在某个打包文件中。要检查服务端有哪些打包格式文件,你需要获取objects/info/packs 文件,这里面包含有打包文件列表(是的,它也是被 update-server-info 所生成的);

=> GET objects/info/packsP pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack这里服务端只有一个打包文件,所以你要的对象显然就在里面。但是你可以先检查它的索引文件以确认。这在服务端有多个打包文件时也很有用,因为这样就可以先检查你所需要的对象空间是在哪一个打包文件里面了:

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx(4k of binary data)现在你有了这个打包文件的索引,你可以看看你要的对象是否在里面 - 因为索引文件列出了这个打包文件所包含的所有对象的SHA值,和该对象存在于打包文件中的偏移量,所以你只需要简单地获取整个打包文件:

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack(13k of binary data)现在你也有了这个 tree 对象,你可以继续在 commit 对象上漫游。它们全部都在这个你已经下载到的打包文件里面,所以你不用继续向服务端请求更多下载了。 在这完成之后,由于下载开始时已探明HEAD引用是指向master 分支, Git 会将它检出到工作目录。

整个过程看起来就像这样:

$ git clone http://github.com/schacon/simplegit-progit.gitInitialized empty Git repository in /private/tmp/simplegit-progit/.git/got ca82a6dff817ec66f44342007202690a93763949walk ca82a6dff817ec66f44342007202690a93763949got 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7Getting alternates list for http://github.com/schacon/simplegit-progit.gitGetting pack list for http://github.com/schacon/simplegit-progit.gitGetting index for pack 816a9b2334da9953e530f27bcac22082a9f5b835Getting pack 816a9b2334da9953e530f27bcac22082a9f5b835 which contains cfda3bf379e4f8dba8717dee55aab78aef7f4dafwalk 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7walk a11bef06a3f659402fe7563abf99ad00de2209e6智能协议
这个HTTP方法是很简单但效率不是很高。使用智能协议是传送数据的更常用的方法。这些协议在远端都有Git智能型进程在服务 - 它可以读出本地数据并计算出客户端所需要的,并生成合适的数据给它,这有两类传输数据的进程:一对用于上传数据和一对用于下载。

上传数据
为了上传数据至远端, Git 使用 send-pack 和 receive-pack 进程。这个 send-pack 进程运行在客户端上,它连接至远端运行的 receive-pack 进程。

举例来说,你在你的项目上运行了 git push origin master, 并且 origin 被定义为一个使用SSH协议的URL。 Git 会使用send-pack 进程,它会启动一个基于SSH的连接到服务器。它尝试像这样透过SSH在服务端运行命令:

$ ssh -x git@github.com "git-receive-pack 'schacon/simplegit-progit.git'"005bca82a6dff817ec66f4437202690a93763949 refs/heads/master report-status delete-refs003e085bb3bcb608e1e84b2432f8ecbe6306e7e7 refs/heads/topic0000这里的 git-receive-pack 命令会立即对它所拥有的每一个引用响应一行 - 在这个例子中,只有 master 分支和它的SHA值。这里第1行也包含了服务端的能力列表(这里是report-status 和 delete-refs)。

每一行以4字节的十六进制开始,用于指定整行的长度。你看到第1行以005b开始,这在十六进制中表示91,意味着第1行有91字节长。下一行以003e起始,表示有62字节长,所以需要读剩下的62字节。再下一行是0000开始,表示服务器已完成了引用列表过程。

现在它知道了服务端的状态,你的 send-pack 进程会判断哪些 commit 是它所拥有但服务端没有的。针对每个引用,这次推送都会告诉对端的receive-pack 这个信息。举例说,如果你在更新 master 分支,并且增加 experiment 分支,这个send-pack 将会是像这样:

0085ca82a6dff817ec66f44342007202690a93763949  15027957951b64cf874c3557a0f3547bd83b3ff6 refs/heads/master report-status00670000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d refs/heads/experiment0000这里的全’0’的SHA-1值表示之前没有过这个对象 - 因为你是在添加新的 experiment 引用。如果你在删除一个引用,你会看到相反的: 就是右边是全’0’。

Git 针对每个引用发送这样一行信息,就是旧的SHA值,新的SHA值,和将要更新的引用的名称。第1行还会包含有客户端的能力。下一步,客户端会发送一个所有那些服务端所没有的对象的一个打包文件。最后,服务端以成功(或者失败)来响应:

000Aunpack ok下载数据
当你在下载数据时, fetch-pack 和 upload-pack 进程就起作用了。客户端启动 fetch-pack 进程,连接至远端的 upload-pack 进程,以协商后续数据传输过程。

在远端仓库有不同的方式启动 upload-pack 进程。你可以使用与 receive-pack 相同的透过SSH管道的方式,也可以通过 Git 后台来启动这个进程,它默认监听在9418号端口上。这里fetch-pack 进程在连接后像这样向后台发送数据:

003fgit-upload-pack schacon/simplegit-progit.git\0host=myserver.com\0它也是以4字节指定后续字节长度的方式开始,然后是要运行的命令,和一个空字节,然后是服务端的主机名,再跟随一个最后的空字节。 Git 后台进程会检查这个命令是否可以运行,以及那个仓库是否存在,以及是否具有公开权限。如果所有检查都通过了,它会启动这个upload-pack 进程并将客户端的请求移交给它。

如果你透过SSH使用获取功能, fetch-pack 会像这样运行:

$ ssh -x git@github.com "git-upload-pack 'schacon/simplegit-progit.git'"不管哪种方式,在 fetch-pack 连接之后, upload-pack 都会以这种形式返回:

0088ca82a6dff817ec66f44342007202690a93763949 HEAD\0multi_ack thin-pack \  side-band side-band-64k ofs-delta shallow no-progress include-tag003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master003e085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 refs/heads/topic0000这与 receive-pack 响应很类似,但是这里指的能力是不同的。而且它还会指出HEAD引用,让客户端可以检查是否是一份克 隆。

在这里, fetch-pack 进程检查它自己所拥有的对象和所有它需要的对象,通过发送 “want” 和所需对象的SHA值,发送 “have” 和所有它已拥有的对象的SHA值。在列表完成时,再发送 “done” 通知upload-pack 进程开始发送所需对象的打包文件。这个过程看起来像这样:

0054want ca82a6dff817ec66f44342007202690a93763949 ofs-delta0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e700000009done这是传输协议的一个很基础的例子,在更复杂的例子中,客户端可能会支持 multi_ack 或者 side-band 能力;但是这个例子中展示了智能协议的基本交互过程。


作者: compare2000    时间: 2014-05-22 18:03
9.7  维护及数据恢复
你时不时的需要进行一些清理工作 ── 如减小一个仓库的大小,清理导入的库,或是恢复丢失的数据。本节将描述这类使用场景。

维护
Git 会不定时地自动运行称为 “auto gc” 的命令。大部分情况下该命令什么都不处理。不过要是存在太多松散对象 (loose object, 不在 packfile 中的对象) 或 packfile,Git 会进行调用git gc 命令。 gc 指垃圾收集 (garbage collect),此命令会做很多工作:收集所有松散对象并将它们存入 packfile,合并这些 packfile 进一个大的 packfile,然后将不被任何 commit 引用并且已存在一段时间 (数月) 的对象删除。

可以手工运行 auto gc 命令:

$ git gc --auto再次强调,这个命令一般什么都不干。如果有 7,000 个左右的松散对象或是 50 个以上的 packfile,Git 才会真正调用 gc 命令。可能通过修改配置中的gc.auto 和 gc.autopacklimit 来调整这两个阈值。

gc 还会将所有引用 (references) 并入一个单独文件。假设仓库中包含以下分支和标签:

$ find .git/refs -type f.git/refs/heads/experiment.git/refs/heads/master.git/refs/tags/v1.0.git/refs/tags/v1.1这时如果运行 git gc, refs 下的所有文件都会消失。Git 会将这些文件挪到 .git/packed-refs 文件中去以提高效率,该文件是这个样子的:

$ cat .git/packed-refs# pack-refs with: peeledcac0cab538b970a37ea1e769cbbde608743bc96d refs/heads/experimentab1afef80fac8e34258ff41fc1b867c702daa24b refs/heads/mastercac0cab538b970a37ea1e769cbbde608743bc96d refs/tags/v1.09585191f37f7b0fb9444f35a9bf50de191beadc2 refs/tags/v1.1^1a410efbd13591db07496601ebc7a059dd55cfe9当更新一个引用时,Git 不会修改这个文件,而是在 refs/heads 下写入一个新文件。当查找一个引用的 SHA 时,Git 首先在refs 目录下查找,如果未找到则到 packed-refs 文件中去查找。因此如果在 refs 目录下找不到一个引用,该引用可能存到packed-refs 文件中去了。

请留意文件最后以 ^ 开头的那一行。这表示该行上一行的那个标签是一个 annotated 标签,而该行正是那个标签所指向的 commit 。

数据恢复
在使用 Git 的过程中,有时会不小心丢失 commit 信息。这一般出现在以下情况下:强制删除了一个分支而后又想重新使用这个分支,hard-reset 了一个分支从而丢弃了分支的部分 commit。如果这真的发生了,有什么办法把丢失的 commit 找回来呢?

下面的示例演示了对 test 仓库主分支进行 hard-reset 到一个老版本的 commit 的操作,然后恢复丢失的 commit 。首先查看一下当前的仓库状态:

$ git log --pretty=onelineab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit484a59275031909e19aadb7c92262719cfcdf19a added repo.rb1a410efbd13591db07496601ebc7a059dd55cfe9 third commitcac0cab538b970a37ea1e769cbbde608743bc96d second commitfdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit接着将 master 分支移回至中间的一个 commit:

$ git reset --hard 1a410efbd13591db07496601ebc7a059dd55cfe9HEAD is now at 1a410ef third commit$ git log --pretty=oneline1a410efbd13591db07496601ebc7a059dd55cfe9 third commitcac0cab538b970a37ea1e769cbbde608743bc96d second commitfdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit这样就丢弃了最新的两个 commit ── 包含这两个 commit 的分支不存在了。现在要做的是找出最新的那个 commit 的 SHA,然后添加一个指它它的分支。关键在于找出最新的 commit 的 SHA ── 你不大可能记住了这个 SHA,是吧?

通常最快捷的办法是使用 git reflog 工具。当你 (在一个仓库下) 工作时,Git 会在你每次修改了 HEAD 时悄悄地将改动记录下来。当你提交或修改分支时,reflog 就会更新。git update-ref 命令也可以更新 reflog,这是在本章前面的 “Git References” 部分我们使用该命令而不是手工将 SHA 值写入 ref 文件的理由。任何时间运行git reflog 命令可以查看当前的状态:

$ git reflog1a410ef HEAD@{0}: 1a410efbd13591db07496601ebc7a059dd55cfe9: updating HEADab1afef HEAD@{1}: ab1afef80fac8e34258ff41fc1b867c702daa24b: updating HEAD可以看到我们签出的两个 commit ,但没有更多的相关信息。运行 git log -g 会输出 reflog 的正常日志,从而显示更多有用信息:

$ git log -g commit 1a410efbd13591db07496601ebc7a059dd55cfe9 Reflog: HEAD@{0} (Scott Chacon ) Reflog message: updating HEAD Author: Scott Chacon Date: Fri May 22 18:22:37 2009 -0700 third commit commit ab1afef80fac8e34258ff41fc1b867c702daa24b Reflog: HEAD@{1} (Scott Chacon ) Reflog message: updating HEAD Author: Scott Chacon Date: Fri May 22 18:15:24 2009 -0700 modified repo a bit    看起来弄丢了的 commit 是底下那个,这样在那个 commit 上创建一个新分支就能把它恢复过来。比方说,可以在那个 commit (ab1afef) 上创建一个名为recover-branch 的分支:

$ git branch recover-branch ab1afef$ git log --pretty=oneline recover-branchab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit484a59275031909e19aadb7c92262719cfcdf19a added repo.rb1a410efbd13591db07496601ebc7a059dd55cfe9 third commitcac0cab538b970a37ea1e769cbbde608743bc96d second commitfdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit酷!这样有了一个跟原来 master 一样的 recover-branch 分支,最新的两个 commit 又找回来了。接着,假设引起 commit 丢失的原因并没有记录在 reflog 中 ── 可以通过删除recover-branch 和 reflog 来模拟这种情况。这样最新的两个 commit 不会被任何东西引用到:

$ git branch -D recover-branch$ rm -Rf .git/logs/因为 reflog 数据是保存在 .git/logs/ 目录下的,这样就没有 reflog 了。现在要怎样恢复 commit 呢?办法之一是使用git fsck 工具,该工具会检查仓库的数据完整性。如果指定 --ful 选项,该命令显示所有未被其他对象引用 (指向) 的所有对象:

$ git fsck --fulldangling blob d670460b4b4aece5915caf5c68d12f560a9fe3e4dangling commit ab1afef80fac8e34258ff41fc1b867c702daa24bdangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9dangling blob 7108f7ecb345ee9d0084193f147cdad4d2998293本例中,可以从 dangling commit 找到丢失了的 commit。用相同的方法就可以恢复它,即创建一个指向该 SHA 的分支。

移除对象
Git 有许多过人之处,不过有一个功能有时却会带来问题:git clone 会将包含每一个文件的所有历史版本的整个项目下载下来。如果项目包含的仅仅是源代码的话这并没有什么坏处,毕竟 Git 可以非常高效地压缩此类数据。不过如果有人在某个时刻往项目中添加了一个非常大的文件,那们即便他在后来的提交中将此文件删掉了,所有的签出都会下载这个 大文件。因为历史记录中引用了这个文件,它会一直存在着。

当你将 Subversion 或 Perforce 仓库转换导入至 Git 时这会成为一个很严重的问题。在此类系统中,(签出时) 不会下载整个仓库历史,所以这种情形不大会有不良后果。如果你从其他系统导入了一个仓库,或是发觉一个仓库的尺寸远超出预计,可以用下面的方法找到并移除 大 (尺寸) 对象。

警告:此方法会**提交历史。为了移除对一个大文件的引用,从最早包含该引用的 tree 对象开始之后的所有 commit 对象都会被重写。如果在刚导入一个仓库并在其他人在此基础上开始工作之前这么做,那没有什么问题 ── 否则你不得不通知所有协作者 (贡献者) 去衍合你新修改的 commit 。

为了演示这点,往 test 仓库中加入一个大文件,然后在下次提交时将它删除,接着找到并将这个文件从仓库中永久删除。首先,加一个大文件进去:

$ curl http://kernel.org/pub/software/scm/git/git-1.6.3.1.tar.bz2 > git.tbz2$ git add git.tbz2$ git commit -am 'added git tarball'[master 6df7640] added git tarball 1 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 git.tbz2喔,你并不想往项目中加进一个这么大的 tar 包。最后还是去掉它:

$ git rm git.tbz2rm 'git.tbz2'$ git commit -m 'oops - removed large tarball'[master da3f30d] oops - removed large tarball 1 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 git.tbz2对仓库进行 gc 操作,并查看占用了空间:

$ git gcCounting objects: 21, done.Delta compression using 2 threads.Compressing objects: 100% (16/16), done.Writing objects: 100% (21/21), done.Total 21 (delta 3), reused 15 (delta 1)可以运行 count-objects 以查看使用了多少空间:

$ git count-objects -vcount: 4size: 16in-pack: 21packs: 1size-pack: 2016prune-packable: 0garbage: 0size-pack 是以千字节为单位表示的 packfiles 的大小,因此已经使用了 2MB 。而在这次提交之前仅用了 2K 左右 ── 显然在这次提交时删除文件并没有真正将其从历史记录中删除。每当有人复制这个仓库去取得这个小项目时,都不得不复制所有 2MB 数据,而这仅仅因为你曾经不小心加了个大文件。当我们来解决这个问题。

首先要找出这个文件。在本例中,你知道是哪个文件。假设你并不知道这一点,要如何找出哪个 (些) 文件占用了这么多的空间?如果运行 git gc,所有对象会存入一个 packfile 文件;运行另一个底层命令git verify-pack 以识别出大对象,对输出的第三列信息即文件大小进行排序,还可以将输出定向到 tail 命令,因为你只关心排在最后的那几个最大的文件:

$ git verify-pack -v .git/objects/pack/pack-3f8c0...bb.idx | sort -k 3 -n | tail -3e3f094f522629ae358806b17daf78246c27c007b blob   1486 734 466705408d195263d853f09dca71d55116663690c27c blob   12908 3478 11897a9eb2fba2b1811321254ac360970fc169ba2330 blob   2056716 2056872 5401最底下那个就是那个大文件:2MB 。要查看这到底是哪个文件,可以使用第 7 章中已经简单使用过的 rev-list 命令。若给 rev-list 命令传入 --objects 选项,它会列出所有 commit SHA 值,blob SHA 值及相应的文件路径。可以这样查看 blob 的文件名:

$ git rev-list --objects --all | grep 7a9eb2fb7a9eb2fba2b1811321254ac360970fc169ba2330 git.tbz2接下来要将该文件从历史记录的所有 tree 中移除。很容易找出哪些 commit 修改了这个文件:

$ git log --pretty=oneline -- git.tbz2da3f30d019005479c99eb4c3406225613985a1db oops - removed large tarball6df764092f3e7c8f5f94cbe08ee5cf42e92a0289 added git tarball必须重写从 6df76 开始的所有 commit 才能将文件从 Git 历史中完全移除。这么做需要用到第 6 章中用过的 filter-branch 命令:

$ git filter-branch --index-filter \   'git rm --cached --ignore-unmatch git.tbz2' -- 6df7640^..Rewrite 6df764092f3e7c8f5f94cbe08ee5cf42e92a0289 (1/2)rm 'git.tbz2'Rewrite da3f30d019005479c99eb4c3406225613985a1db (2/2)Ref 'refs/heads/master' was rewritten--index-filter 选项类似于第 6 章中使用的 --tree-filter 选项,但这里不是传入一个命令去修改磁盘上签出的文件,而是修改暂存区域或索引。不能用rm file 命令来删除一个特定文件,而是必须用 git rm --cached 来删除它 ── 即从索引而不是磁盘删除它。这样做是出于速度考虑 ── 由于 Git 在运行你的 filter 之前无需将所有版本签出到磁盘上,这个操作会快得多。也可以用--tree-filter 来完成相同的操作。git rm 的 --ignore-unmatch 选项指定当你试图删除的内容并不存在时不显示错误。最后,因为你清楚问题是从哪个 commit 开始的,使用filter-branch 重写自 6df7640 这个 commit 开始的所有历史记录。不这么做的话会重写所有历史记录,花费不必要的更多时间。

现在历史记录中已经不包含对那个文件的引用了。不过 reflog 以及运行 filter-branch 时 Git 往 .git/refs/original 添加的一些 refs 中仍有对它的引用,因此需要将这些引用删除并对仓库进行 repack 操作。在进行 repack 前需要将所有对这些 commits 的引用去除:

$ rm -Rf .git/refs/original$ rm -Rf .git/logs/$ git gcCounting objects: 19, done.Delta compression using 2 threads.Compressing objects: 100% (14/14), done.Writing objects: 100% (19/19), done.Total 19 (delta 3), reused 16 (delta 1)看一下节省了多少空间。

$ git count-objects -vcount: 8size: 2040in-pack: 19packs: 1size-pack: 7prune-packable: 0garbage: 0repack 后仓库的大小减小到了 7K ,远小于之前的 2MB 。从 size 值可以看出大文件对象还在松散对象中,其实并没有消失,不过这没有关系,重要的是在再进行推送或复制,这个对象不会再传送出去。如果真的要完全把这个对象删除,可以运行git prune --expire 命令。



9.8  总结
现在你应该对 Git 可以作什么相当了解了,并且在一定程度上也知道了 Git 是如何实现的。本章覆盖了许多 plumbing 命令 ── 这些命令比较底层,且比你在本书其他部分学到的 porcelain 命令要来得简单。从底层了解 Git 的工作原理可以帮助你更好地理解为何 Git 实现了目前的这些功能,也使你能够针对你的工作流写出自己的工具和脚本。

Git 作为一套 content-addressable 的文件系统,是一个非常强大的工具,而不仅仅只是一个 VCS 供人使用。希望借助于你新学到的 Git 内部原理的知识,你可以实现自己的有趣的应用,并以更高级便利的方式使用 Git。

作者: compare2000    时间: 2014-05-23 23:59
watch命令可以间断性地运行某个应用程序,并显示输出。
语法watch  [-dhvt]  [-n <seconds>] [--differences[=cumulative]] [--help] [--interval=<seconds>] [--no-title] [--version] <com-mand>
参数选项-n或—interval
    watch缺省每2秒运行一下程序,可以用-n或–interval来指定间隔的时间。
-d或–differences
用-d或–differences 选项watch 会高亮显示变化的区域。而—cumulative  选项会把变动过的地方(不管最近的那次有没有变动)都高亮显示出来。
-t 或–no-title
-t 或–no-title 选项会关闭watch命令在顶部的时间间隔、命令、当前时间的输出。
样例


watch –differences=cumulative uptime
watch -t –differences=cumulative uptime

linux终端中输出彩色字体(C/SHELL)  这几天在用libvlc的时候看到它在terminal里面输出彩色字体觉得挺好玩的,以为是用ncurses实现的,后来一查原来用ANSI C的转义字符就可以实现,不过好现只在linux下有效吧, windows神马的貌似不行,把项目终端凌乱的输出信息整理了一下,关键字符也都用彩色显示,看上去舒服多了,写一下用法以防止以后忘了再去查。

先把控制码列出来(从网上搜来的):

\033[0m               关闭所有属性

\033[1m                       设置高亮度

\033[4m                       下划线

\033[5m                        闪烁

\033[7m                        反显

\033[8m                        消隐

\033[30m----\33[37m 设置前景色

\033[40m----\33[47m 设置背景色

\033[nA                        光标上移n行

\033[nB                       光标下移n行

\033[nC                       光标右移n行

\033[nD                       光标左移n行

\033[y;xH                     设置光标位置

\033[2J                        清屏

\033[K                         清除从光标到行尾的内容

\033[s                          保存光标位置

\033[u                          恢复光标位置

\033[?25l                      隐藏光标

\033[?25h                     显示光标

背景色:
40:黑 41:深红 42:绿 43:黄色 44:蓝色 45:紫色 46:深绿 47:白色
前景色:
30:黑 31:红 32:绿 33:黄 34:蓝色 35:紫色 36:深绿 37:白色

控制字符是打开某种样式,输出完成时需要再关闭样式才能使terminal恢复到原来状态,简单例子:

printf("\e[32m%s\e[0m\n", "hello world");

\033和\e是一回事,使用\e会更简单一些,输出为绿色字体,如下图:


\e[32m为打开绿色前景色样式,\e[0m为关闭所有样式,如果未关闭,则所有输出字体均为绿色前景色,如下代码:

         printf("\e[32m%s\e[0m\n", "hello world");

输出效果如下:


绿色高亮代码如下:

printf("\e[32m\e[1m%s\e[0m\n", "hello world");

输出效果如下:


另外光标移动的控制码在输出类似于下载进度的信息时用得到。

在shell中也可以直接用echo输出,需要加-e选项打开转义字符解释,如输出高亮的绿色字体为:

echo -e "\e[32m\e[1mhello world\e[0m"

其它的我也没怎么看,觉得也就改变字体颜色和移动光标位置能用得到,觉得挺好玩的就写下来以后忘了查一查。
linux常用命令  uname -a    查看内核版本      
ls -al    显示所有文件的属性
pwd         显示当前路径      
cd -    返回上一次目录     cd ~    返回主目录
date s      设置时间、日期         
cal      显示日历     cal 2006
bc          计算器具              
man  & info     帮助手册
locale     显示当前字体     locale -a    所有可用字体     /etc/sysconfig/i18n设置文件
LANG=en    使用英文字体           
sync       将数据同步写入硬盘      
shutdonw -h now & half & poweroff  关机
reboot     重启                  
startx  &  init 5   进入图形介面
/work  & ?work    向上、下查找文档内容
chgrp      改变档案群组  chgrp testing install.log   
chown     改变所属人   chown root:root install.log
chmod      改变属性     chmod 777 install.log     read=4  write=2  execute=1
cp   复制   cp filename
rm   删除文件  rm -rf filename   强制删除文件
rmdir   删除文件夹
mv  移动    mv 123.txt 222.txt  重命名
mkdir     创建文件夹
touch     创建文件  更新当前时间
cat       由第一行开始显示     cat |more  分页
nl        在内容前加行号
more  &  less   一面一面翻动
head -n filename   显示第N行内容
tail -n filename  显示后N行内容
od        显示非纯文档
df -h 显示分区空间
du  显示目录或文件的大小
fdisk   分区设置    fdisk -l /dev/hda  显示硬盘分区状态
mkfs    建立各种文件系统  mkfs -t ext3  /dev/ram15  
fsck    检查和修复LINUX档案
ln      硬链接   ln -s  软件链接
whereis   查找命令
locate    查找
find      查找   find / -name "***.***"
which     查看工具
whoami    显示当前用户
gcc -v    查看GCC版本
chattr +i filename  禁止删除   chattr -i filename  取消禁止
lsattr    显示隐藏档属性
updatedb  更新资料库
mke2fs    格式化   mkfs -t ext3
dd if=/etc/passwd ōf=/tmp/passwd.bak    备份
mount     列出系统所有的分区
mount -t iso9660 /dev/cdrom /mnt/cdrom   挂载光盘
mount -t vfat /dev/fd0 /mnt/floppy       挂载软盘
mount -t vfat -o iocharset=utf8,umask=000 /dev/hda2 /mnt/hda2   挂载fat32分区
mount -t ntfs -o nls=utf8,umask=000 /dev/hda3 /mnt/hda3         挂载ntfs分区
Linux-NTFS Project: http://linux-ntfs.sourceforge.net/
umount /mnt/hda3  缷载
ifconfig   显示或设置网络设备
service network restart   重启网卡
ifdown eth0  关闭网卡
ifup eth0    开启网卡
clear    清屏
history    历史记录       !55  执行第55个指令
stty   设置终端    stty -a
fdisk /mbr   删除GRUB
at     僅進行一次的工作排程
crontab   循環執行的例行性命令    [e]编辑,[l]显示,[r]删除任务
&       后台运行程序    tar -zxvf 123.tar.gz & --------->后台运行
jobs    观看后台暂停的程序   jobs -l
fg      将后台程序调到前台   fg n ------>n是数字,可以指定进行那个程序
bg      让工作在后台运行
kill    结束进程    kill -9 PID     [9]强制结束,[15]正常结束,[l]列出可用的kill信号
ps aux  查看后台程序  
top     查看后台程序   top -d 2    每两秒更新一次        top -d 2 -p10604   观看某个PID
        top -b -n 2 > /tmp/top.txt ----->將 top 的資訊進行 2 次,然後將結果輸出到 /tmp/top.txt   
pstree   以树状图显示程序    [A]以 ASCII 來連接, 列出PID, [p]列出帐号
killall   要刪除某個服務    killall -9 httpd
free      显示内存状态     free -m  -------->以M为单位显示
uptime    显示目前系统开机时间
netstat   显示网络状态    netstat -tulnp------>找出目前系統上已在監聽的網路連線及其 PID
dmesg     显示开机信息    demsg | more
nice      设置优先权      nice -n -5 vi & ----->用 root 給一個 nice 植為 -5 ,用於執行 vi
renice    调整已存在优先权
runlevel  显示目前的runlevel
depmod    分析可载入模块的相依性
lsmod     显示已载入系统的模块
modinfo   显示kernel模块的信息
insmod    载入模块
modprobe   自动处理可载入模块
rmmod     删除模块
chkconfig   检查,设置系统的各种服务     chkconfig --list ----->列出各项服务状态
ntsysv     设置系统的各种服务
cpio      备份文件


压缩命令:
*.Z      compress 程式壓縮的檔案;
*.bz2    bzip2 程式壓縮的檔案;
*.gz     gzip 程式壓縮的檔案;
*.tar    tar 程式打包的資料,並沒有壓縮過;
*.tar.gz tar 程式打包的檔案,其中並且經過 gzip 的壓縮
compress filename  压缩文件  加[-d]解压  uncompress
gzip filename   压缩  加[-d]解压  zcat 123.gz 查看压缩文件内容
bzip2 -z filename  压缩  加[-d]解压   bzcat filename.bz2  查看压缩文件内容
tar -cvf /home/123.tar /etc  打包,不压缩
tar -xvf 123.tar   解开包
tar -zxvf /home/123.tar.gz  以gzip解压
tar -jxvf /home/123.tar.bz2  以bzip2解压
tar -ztvf /tmp/etc.tar.gz   查看tar内容
cpio -covB  > [file|device]   份份
cpio -icduv < [file|device]   还原

vi一般用法
一般模式              编辑模式                  指令模式
h 左               a,i,r,o,A,I,R,O             :w 保存
j 下                进入编辑模式                :w! 强制保存
k 上                dd 删除光标当前行           :q! 不保存离开
l 右                ndd 删除n行                 :wq! 保存后离开
0 移动到行首        yy 复制当前行                :e! 还原原始档
$ 移动到行尾        nyy 复制n行                  :w filename 另存为
H 屏幕最上          p,P 粘贴                     :set nu 设置行号
M 屏幕中央          u  撤消                      :set nonu 取消行号
L 屏幕最下          [Ctrl]+r 重做上一个动作       ZZ 保存离开
G 档案最后一行      [ctrl]+z 暂停退出            :set nohlsearch   永久地关闭高亮显示
/work 向下搜索                                   :sp 同时打开两个文档
?work 向上搜索                                   [Ctrl]+w 两个文档设换
gg 移动到档案第一行                              :nohlsearch    暂时关闭高亮显示

认识SHELL
alias    显示当前所有的命令别名      alias lm="ls -al"   命令别名    unalias lm 取消命令别名
type      类似which
exprot    设置或显示环境变量
exprot PATH="$PATH":/sbin  添加/sbin入PATH路径
echo $PATH    显示PATH路径
bash      进入子程序
name=yang     设定变量
unset name    取消变量
echo $name    显示变量的内容
myname="$name its me"   &   myname='$name its me'     单引号时$name失去变量内容
ciw=/etc/sysconfig/network-scrīpts/     设置路径
env      列出所有环境变量
echo $RANDOM    显示随意产生的数
set      设置SHELL
PS1='[\u@\h \w \A #\#]\$ '     提示字元的設定
   [root@linux ~]# read [-pt] variable     -----------读取键盘输入的变量
   參數:
   -p  :後面可以接提示字元!
   -t  :後面可以接等待的『秒數!』
declare    声明 shell 变量
ulimit -a   显示所有限制资料
ls /tmp/yang && echo "exist" || echo "not exist"
意思是說,當 ls /tmp/yang 執行後,若正確,就執行echo "exist" ,若有問題,就執行echo "not exist"
echo $PATH | cut -d ':' -f 5       以:为分隔符,读取第5段内容
export | cut -c 10-20      读取第10到20个字节的内容
last | grep 'root'    搜索有root的一行,加[-v]反向搜索
cat /etc/passwd | sort    排序显示
cat /etc/passwd | wc      显示『行、字数、字节数』
正规表示法
[root@test root]# grep [-acinv] '搜尋字串' filename
       參數說明:
       -a :將 binary 檔案以 text 檔案的方式搜尋資料
       -c :計算找到 '搜尋字串' 的次數
       -i :忽略大小寫的不同,所以大小寫視為相同
       -n :順便輸出行號
       -v :反向選擇,亦即顯示出沒有 '搜尋字串' 內容的那一行!
grep -n 'the' 123.txt     搜索the字符 -----------搜尋特定字串      
grep -n 't[ea]st' 123.txt    搜索test或taste两个字符---------利用 [] 來搜尋集合字元
grep -n '[^g]oo' 123.txt     搜索前面不为g的oo-----------向選擇 [^]
grep -n '[0-9]' 123.txt  搜索有0-9的数字
grep -n '^the' 123.txt 搜索以the为行首-----------行首搜索^
grep -n '^[^a-zA-Z]' 123.txt  搜索不以英文字母开头
grep -n '[a-z]$' 123.txt    搜索以a-z结尾的行---------- 行尾搜索$
grep -n 'g..d' 123.txt     搜索开头g结尾d字符----------任意一個字元 .
grep -n 'ooo*' 123.txt     搜索至少有两个oo的字符---------重複字元 *
sed    文本流编辑器    利用脚本命令来处理文本文件
awd    模式扫描和处理语言
nl 123.txt | sed '2,5d'   删除第二到第五行的内容
diff     比较文件的差异
cmp      比较两个文件是否有差异
patch    修补文件
pr       要打印的文件格式化


帐号管理
/etc/passwd    系统帐号信息
/etc/shadow    帐号密码信息    经MD5 32位加密
     在密码栏前面加『 * 』『 ! 』禁止使用某帐号
/etc/group     系统群组信息
/etc/gshadow
newgrp    改变登陆组
useradd  &  adduser    建立新用户  ---------> useradd -m test  自动建立用户的登入目录
          useradd -m -g pgroup test --------->指定所属级
/etc/default/useradd   相关设定
/etc/login.defs       UID/GID 有关的設定
passwd    更改密码 -----------> passwd test
usermod   修改用户帐号
userdel   删除帐号 ----------->userdel -r test
chsh      更换登陆系统时使用的SHELL   [-l]显示可用的SHELL;[-s]修改自己的SHELL
chfn      改变finger指令显示的信息
finger    查找并显示用户信息
id        显示用户的ID ----------->  id test
groupadd   添加组
groupmod   与usermod类似
groupdel   删除组
su test    更改用户   su -    进入root,且使用root的环境变量
sudo       以其他身份来执行指令
visudo     编辑/etc/sudoers      加入一行『 test ALL=(ALL) ALL 』
           %wheel ALL = (ALL) ALL               系统里所有wheel群组的用户都可用sudo
           %wheel ALL = (ALL) NOPASSWD: ALL     wheel群组所有用户都不用密码NOPASSWD
       User_Alias ADMPW = vbird, dmtsai, vbird1, vbird3         加入ADMPW组
       ADMPW ALL = NOPASSWD: !/usr/bin/passwd, /usr/bin/passwd [A-Za-z]*, \
       !/usr/bin/passwd root      可以更改使用者密码,但不能更改root密码 (在指令前面加入 ! 代表不可)
PAM (Pluggable Authentication Modules, 嵌入式模組)
who & w     看谁在线                    
last        最近登陆主机的信息
lastlog     最近登入的時間    读取 /var/log/lastlog
talk        与其他用户交谈
write       发送信息    write test   [ctrl]+d 发送
mesg        设置终端机的写入权限    mesg n 禁止接收     mesg y
wall        向所有用户发送信息    wall this is q test
mail        写mail  
/etc/default/useradd    家目录默认设置
quota      显示磁盘已使用的空间与限制     quota -guvs ----->秀出目前 root 自己的 quota 限制值
           quota -vu   查询
quotacheck   检查磁盘的使用空间与限制     quotacheck -avug  ----->將所有的在 /etc/mtab 內,含有 quota 支援的 partition 進行掃瞄
             [-m] 强制扫描
     quota一定要是独立的分区,要有quota.user和quota.group两件文件,在/etc/fstab添加一句:
     /dev/hda3 /home ext3 defaults,usrquota,grpquota 1 2
     chmod 600 quota*         设置完成,重启生效
edquota    编辑用户或群组的quota  用户,[g]群组,[p]复制,[t]设置宽限期限
           edquota -a yang       edquota -p yang -u young ----->复制   
quotaon    开启磁盘空间限制     quotaon -auvg -------->启动所有的具有 quota 的 filesystem
quotaoff   关闭磁盘空间限制     quotaoff -a  -------->关闭了 quota 的限制
repquota -av     查閱系統內所有的具有 quota 的 filesystem 的限值狀態
Quota 从开始準備 filesystem 的支援到整個設定結束的主要的步驟大概是:
1、設定 partition 的 filesystem 支援 quota 參數:
由於 quota 必須要讓 partition 上面的 filesystem 支援才行,一般來說, 支援度最好的是 ext2/ext3 ,
其他的 filesystem 類型鳥哥我是沒有試過啦! 启动 filesystem 支援 quota 最简单就是编辑 /etc/fstab ,
使得準備要开放的 quota 磁碟可以支援 quota 囉;
2、建立 quota 記錄檔:
剛剛前面講過,整個 quota 進行磁碟限制值記錄的檔案是 aquota.user/aquota.group,
要建立這兩個檔案就必須要先利用 quotacheck 掃瞄才行喔!
3、编辑 quota 限制值資料:
再來就是使用 edquota 來编辑每個使用者或群組的可使用空間囉;
4、重新掃瞄與启动 quota :
設定好 quota 之後,建議可以再進行一次 quotacheck ,然後再以 quotaon 來启动吧!

开机流程简介
1、載入 BIOS 的硬體資訊,並取得第一個关机裝置的代號;
2、讀取第一個关机裝置的 MBR 的 boot Loader (亦即是 lilo, grub, spfdisk 等等) 的关机資訊;
3、載入 Kernel 作業系統核心資訊, Kernel 开始解壓縮,並且嘗試驅動所有硬體裝置;
4、Kernel 執行 init 程式並取得 run-level 資訊;
5、init 執行 /etc/rc.d/rc.sysinit 檔案;
6、启动核心的外掛模組 (/etc/modprobe.conf);
7、init 執行 run-level 的各個批次檔( scripts );
8、init 執行 /etc/rc.d/rc.local 檔案;
9、執行 /bin/login 程式,並等待使用者登入;
10、登入之後开始以 Shell 控管主机。
在/etc/rc.d/rc3.d內,以S开头的为开机启动,以K开头的为关闭,接着的数字代表执行顺序
GRUB vga设定
彩度\解析度  640x480  800x600  1024x768  1280x1024   bit
    256        769      771      773       775      8 bit
   32768       784      787      790       793     15 bit
   65536       785      788      791       794     16 bit
   16.8M       786      789      792       795     32 bit

./configure    检查系统信息       ./configure --help | more  帮助信息
make clean     清除之前留下的文件
make           编译
make install   安装
rpm -q  ----->查询是否安装             rpm -ql ------>查询该套件所有的目录
rpm -qi ----->查询套件的说明资料       rpm -qc[d] ----->设定档与说明档
rpm -ivh  ---->安装                    rpm -V  -------->查看套件有否更动过
rpm -e  ------>删除                    rpm -Uvh ------->升级安装
--nodeps ----->强行安装                --test ----->测试安装
du -sh /home/* 可以查看目前系统用户已经使用的空间,但其不是按大小顺序排列的
smbpassword -a 添加一个samba用户


作者: compare2000    时间: 2014-05-24 00:00
配置linux登录欢迎信息
直接在/etc/motd文件中输入即可

命令:hostname   显示机器名
   修改机器名:修改文件/etc/HOSTNAME,修改后重启本地网络文件:/etc/init.d/boot.localnet start
              同时,也要将/etc/hosts文件中IP对应的机器名也修改一下
   或者使用 hostname <new hostname> 命令实时修改机器名(此时,还需要修改/etc/HOSTNAME 和 /etc/hosts文件,以便重启后还会生效)

命令:mutt   发送邮件
linux的配置:
1)通过windows的“ipconfig /all” 命令可以知道linux服务器当前的DNS服务器
2)通过yast命令的“Network Services/DNS and Host Name/”,
        修改域名为“huawei”,“Name Server 1/Name Server 2”为DNS服务器IP地址
      或者直接修改/etc/resolv.conf文件,其格式类似如下:
        nameserver 202.96.128.86
        nameserver 202.96.64.68
        search huawei
3)执行“/etc/rc.d/postfix start”命令
命令格式:
echo "邮件内容" | mutt -s "邮件标题"  -a "附件1" -a "附件2" zhuchunjian@huawei.com jihongyi@huawei.com

江西局点部署iptv_check,按照操作指导添加DNS并启动邮件发送功能后,还是发不了邮件,
后来跟现场用服研究了下,是交换机策略问题,需要放开这两个应用的端口,
这在其他局点也可能会碰到,最好在安装文档中说明下。

======================================================

在交换机上做的安全策略

rule 5 permit udp source-port eq dns destination 115.153.255.87 0

rule 10 permit tcp source-port eq smtp destination 115.153.255.87 0


命令:vi 编辑文件
        有时候vi会core,则删除~/.viminfo文件就可能解决,并用 "killall -9 vim; killall -9 vi" 杀掉已挂起的vi进程
        ctrl+v 进入列模式
        fx     表示查找,在当前行中向右搜素字符‘x’并定位到该字符出现的第一个位置。“;”重复该命令。 “,”重复该命令,但向左搜索
        Fx     同fx,只是方向相反。
        tx     表示to,同fx,只是光标定位在该字符的前一个位置。
        Tx     同tx,只是方向相反。
        H      (Home)光标定位到当前屏幕的最上方;    M (Middle)光标定位到屏幕的中间;   L (Last)光标定位到屏幕的最下方
        Ctrl+d 屏幕向下滚动半屏          Ctrl+u 屏幕向上滚动半屏
        Ctrl+f 屏幕向下滚动一屏          Ctrl+b 屏幕向上滚动一半屏
        Ctrl+e 向上滚动一行,光标行不变(光标到达屏幕的最上方,光标行才会变化)
        Ctrl+y 向下滚动一行,光标行不变(光标到达屏幕的最下方,光标行才会变化)
        zz     将当前光标行置于屏幕中间
        zt     将当前光标行置于屏幕最上方
        zb     将当前光标行置于屏幕最下方
        打开一个比较长的文档,跟我做 zfG , 现在是不是全部文章都折叠成一行,高亮显示了?
        这里 zf 是创建一个 folder,G 是移动到文件尾部,这样也就全都装进来了,也可以用其他的移动手段,呵呵
        然后 zo 就是打开折叠文本,zc 是再把它们折叠起来。如果有很多折叠文本,用zr 就把它们全部打开了,zm 是把它们全部折叠。如果有嵌套的折叠文本,可以用zR 和 zM 一次完                成打开和关闭。zn 是关闭折叠功能,zN 是重新打开折叠功能,zi是在两者间切换。
        折叠成一行的文本可以在 visual 模式下被剪切、复制、粘贴,很方便。

        50%    表示定位到当前文件的50%处,50表示百分比。
        30G    表示定位到当前文件的第30行。
        gg     定位到第一行,同1G                     G   定位到文件最后一行,同100%
        Ctrl+g 显示文件名,以及光标位置(g也可以大写)
        
        %      括弧匹配
        *      在某个word处,按*表示以当前word为关键字,进行全字匹配搜索,并定位到下一个出现的位置。
        3*     表示定位到下面第3个出现的位置
        g*     表示当前word可以包含在其他word中(即,非全字匹配),也可以用“3g*”定位到第3个出现的位置
        #      同*,只是搜索方向为向上

        v或V   进入visual模式,此时,o命令表示跳转到选择文本的另一头(other end),O命令在左右两角移动
        
        yw     复制一个单词到寄存器中;
        yy     复制一行到寄存器中;        p  从当前寄存器中粘贴
        "xyy   复制一行到寄存器x中;       "xp从寄存器x中粘贴

        :iab <缩写> <实际字符串>        定义缩写字符(iabbrev);                   :abbreviate  显示所有的缩写
        :abclear   清除所有缩写
        
        :e!    放弃当前所有的编辑,并重新加载该文件的原始内容
        :set number           显示行号;   :set nonumber  关闭行号
        :set ruler            在右下角显示关闭位置
        :set ic               忽略大小写   :set noic      取消忽略大小写
        :set hlsearch         高亮显示     :set nohlsearch取消高亮显示
        :set incsearch        增量搜索
        :set nowrapscan       非折叠搜索
        :set shiftwidth=4    设置自动缩进 4 个空格, 当然要设自动缩进先.
        :set sts=4           即设置 softtabstop 为 4. 输入 tab 后就跳了 4 格.
        :set tabstop=4       实际的 tab 即为 4 个空格, 而不是缺省的 8 个.
        :set expandtab       在输入 tab 后, vim 用恰当的空格来填充这个 tab.
        :set paste           进入粘贴模式,粘贴的内容将不会缩进
        :next         下一个文件;        :prev  上一个文件;        :args  文件列表
   宏:
        q{register}        开始录制宏,录制结束时按q退出
        @{register}        运行宏
   标记:
        ``(2个反撇号)        跳回到上次光标的位置
        Ctrl+i               跳回到上次更新的位置
        m{mark}              设置标签;                `{mark} (反撇号)跳到标签出;        :marks    显示所有标签

命令:du 查看目录大小
        du -sk .    显示单位为KB
        du -sh .    显示单位会自动调整
        du -sh *    显示当前目录下的所有文件和目录大小

命令:find  查找
        find命令参数中的数字n含义: +n  表示 “> n”; -n 表示 “< n”;  n 表示 “= n”
        如, find . -mmin +2  表示查找当前目录下、修改时间大于2分钟的文件(即,2分钟之前的文件)
             find . -mmin -2  表示查找当前目录下、修改时间小于2分钟的文件(即,2分钟之后的文件)
        -amin n     文件访问时间在n 分钟前
        -atime n    文件访问时间在n 天前
        -mmin n     文件修改时间在n 分钟前
        -mtime n    文件修改时间在n 天前
        -size n[bckw]     文件大小,b 表示单位为block(块,512字节),默认单位;
                                    c 表示单位为字节; k 表示单位为KB;  w 表示单位为2个字节
        -type c     指定文件类型,c取值: d 表示目录; f 表示普通文件; l 表示link文件; s 表示sock; p 表示pipe

        find命令中的条件默认是“与”(-a)的关系,如果需要“或”的关系,则使用-o:
                find . \( -name "*.txt" -o -name "*.csv" \) -a -type f  查找当前目录下的txt或者csv文件
        find . -mtime +5 -name "*.log" -exec tar --remove-files -rf logs.tar {} \;   查找当前目录下、5天前的log文件,并打包
        find . -path "./dir1/dir2/*" -maxdepth 3          只查找./dir1/dir2/目录下的内容,不包括其子目录的内容。
        find . -name "*.csv" -exec bash -c "cat {} >  {}.txt" \;        # exec中使用重定向
        find /home/huawei/mdn2000/rrs/logs/agent/run -printf "%f\n"     # 只打印最后的文件名(不包括路径)
        find . -name "*.sinf" -printf "%p,%Ax %AT\n" |sort -t, -k2r | head -1  # 查找当前目录下最新的*.sinf文件



命令:mount  挂载存储
格式: mount -a [-fv] [-t vfstype] [-n] [-rw] [-F] <device> <dir>
        mount -o loop <iso_file> <dest_dir>        解开iso文件到<dest_dir>目录下
        mount --bind <src_dir> <dest_dir>          mount本地目录
                /etc/fstab:   /mnt/mydir  /home/hms/data/c  auto bind 0 0
        mount -t smbfs //IP/share_dir <dest_dir> -o username=zcj        mount Windows共享目录(回车后需要输入密码)

命令:iptables linux配置防火墙
        iptables -L 显示当前配置;        iptables -F 清空当前所有配置;
        iptables -A INPUT -s 10.164.79.205 -j DROP        限制特定的IP访问本机
        iptables -A INPUT -s 10.168.67.0/24 -j DROP       限定IP段
        iptables -A INPUT -d 10.137.13.77 -p udp -j REJECT
        iptables -A INPUT -p tcp --sport 554 --dport 554 -j REJECT        限制特定端口访问本机

命令cpio  cpio文件操作
        cpio -idF test.cpio        解压

命令:lsof (list opened files)   列举系统中已经被打开的文件
        lsof -n                 显示结果时,不将IP转换为hostname
        lsof -P                 显示结果时,不将Port转换为services名
        lsof -i[46] [protocol][@hostaddr][:port]     用以显示符合条件的进程情况
                4    表示IPv4; 6 表示IPv6
                protocol   表示协议:TCP 或 UDP
                hostaddr   表示主机地址
                port       表示端口号,可以有多个(以逗号分隔)
       示例:      lsof -ni @125.88.104.19:21,22   显示“与125.88.104.19的21或22端口”存在连接的进程信息
        lsof -p <pid>           显示进程<pid>打开的所有文件信息
        lsof <file_name>        显示打开<file_name>文件的所有进程
        lsof +d <dir>           显示<dir>目录下被打开的文件(不会搜索子目录)
        lsof +D <dir>           显示<dir>目录下被打开的文件(会搜索子目录)
        lsof -c <proc_name>     显示<proc_name>进程打开的文件

命令:time 打印程序运行的耗时
TIMEFORMAT="" time ./testprogram
输出:
0.00user 0.00system 0:12.62elapsed 0%CPU (0avgtext+0avgdata 5792maxresident)k
0inputs+8outputs (0major+4060minor)pagefaults 0swaps


命令:date  时间显示与设置
        date MMDDhhmm[[cc]YY][.ss]        设置时间,如date 12171830.00 设置当前时间为12月17日18点30分0秒(年份不变)
        date '+%Y-%m-%d %H:%M:%S.%N' 或者 date '+%F %X.%N'
               年 月 日 时 分 秒 纳秒
        date --date '2 days ago' +%F       获取2天前的日期
        date --date '2 days' +%F           获取2天后的日期
  指定特定日期的格式:  date -d 'string'
        其中,string由以下几个部分组成(各个部分以空格分隔):
            1,calendar date time  表示日期,有多种格式,但用格式 yyyy-mm-dd 即可。
            2,time of day         表示时间,格式:HH:MM[:SS[.XXXXXX]][am|pm][SHHMM],其中,S表示+或-,HH和MM分别表示小时,分钟
                                       如,12:20+0210 表示10:10 ,12:20-0210表示 14:30
            3,time zone           表示时区,如UTC,CST等
            4,day of the week     表示星期几
            5,relative            调整日期,格式: [+|-]整数 单位 [ago],  或者直接用如下字符串:yesterday,tomorrow,now,today,this
                                     单位有:失真的单位(year,month)精确的单位(fortnight(2周),week,day,hour,minute(min),second(sec))
                                     这些单位后可以加(s),也可以不加
            6,pure numbers        精确的date信息,当“calendar date time”未指定时,则日期从此处读取,格式:YYYYMMDD
                                                   当“time of day”未指定时,则时间从此处读取,格式:HHMM

  将指定日期转换为秒:    date -d '2008-10-12' +%s              # 结果为:2008-10-12与1970-01-01的时间差(单位:秒)  
  获取2009-11-12的后3天的日期:  date -d '2009-11-12 UTC 3 days'  +%F             # days 可以用hours,seconds,minutes
  获取2009-11-12的前3天的日期:  date -d '2009-11-12 UTC -3 days' +%F             # days 可以用hours,seconds,minutes
  获取timestamp时间对应的当前日期:time=1331974800
                                   diff=$(echo "$(date +%s) - $time"|bc)
                                   date -d "$diff seconds ago" +%F.%X

命令: 时区的概念
1,不管通过任何渠道我们想要同步系统的时间,通常提供方只会给出UTC+0的时间值而不会提供时区(因为它不知道你在哪里).所以当我们设置系统时间的时候,设置好时区是首先要做的工作
2,linux系统设置时区(有两种方法):
    1)系统预先编译了很多timezone文件,存放在/usr/share/zoneinfo目录下
       可以拷贝自己所在城市的timezone文件到 /etc/localtime
       也可以建一个链接: ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
    2)设置TZ环境变量: TZ='Asia/Beijing'; export TZ (该变量会覆盖/etc/localtime)
        如果不知道如何设置TZ变量值,可以使用tzselect命令选择


/etc/sysconfig/clock



命令: tethereal,tcpdump      抓包命令
        tethereal -ni bond0 host 10.164.79.105 and port 554
        tcpdump   -i any    host 10.164.79.105
       tcpdump -X -s 0 'dst port 554 and ip[2:2] > 60'            ip[2:2] 表示ip报文的第2个字节开始,取2个字节的值

tcpdump -i any  dst host 125.88.104.10 and ! \( 125.88.104.6 or 125.88.104.24 \) and dst port 60000
  类型的关键字: host,net,port
  传输的方向:   src,dst,dst or src, dst and src
  协议:        tcp,udp,ip,arp,rarp
  逻辑运算符:  and,&& ; or ,|| ; not ,!,


命令:echo   显示颜色
        echo -e "\e[背景色;前景色;1m字符串\e[m"
        背景色:40 黑;41 深红;42 绿;43 黄;44 蓝;45 紫;46 深绿;47 白;
        前景色:30 黑;31 红;  32 绿;33 黄;34 蓝;35 紫;36 深绿;37 白;

命令:crontab  配置定时任务
        每个用户都有独立的表格,在/var/spool/cron/tabs/目录下,以用户名命名的文件中
        crontab文件格式如下:(有6个域,以空格分隔)

分钟数
小时数
天数
月数
星期数
命令
范围
0-59
0-23
1-31
1-12
0-7(0和7表示星期天)


        时间域可以的格式:
        1),逗号分隔的列表: 如,分钟数域指定了0,1,2,则表示第0,1,2分钟时执行该命令
        2),范围:           如,分钟数域指定了0-4,则表示第0,1,2,3,4分钟时执行该命令
        3),每隔一段时间:   如,分钟数域指定了*/30,则表示每隔30分钟执行该命令
        4),组合:           如,分钟数域指定了0-4,8-12/2,则表示第0,2,4,8,10,12分钟时执行该命令
  注意:定时任务的命令中不能使用环境变量(除了$HOME,$USER)
        查看当前用户设置的定时任务:        crontab -l
        清除所有的定时任务:                crontab -r        
        手工修改定时任务:                  crontab -e        
        将定时任务存放到临时文件中,然后导入: crontab <临时文件>
        重启定时任务:                      rccron restart

命令:sort    排序
    sort -t, -k 1rn -k2,3
    -t  指定域分隔符,默认分隔符为空格
    -k  指定按第几列排序,r表示倒排,n表示按数字排列

命令:ls   列出文件
        ls -S 按文件大小倒序排列(-r 表示倒序排列)
        ls -t 按修改时间倒序排列

命令:uniq    唯一
        必须先排序,才能使用该命令
        uniq -c         将每一项出现的次数显示在前面

命令:tr   删除字符
        小写转大写        tr "[:lower:]" "[:upper:]"        或 tr "[a-z]" "[A-Z]"
        删除字符          tr -d "字符"
        删除重复字符,只保留第一个        tr -s "字符"
        删除文件中的^M    tr -s "[\r]" "[\n]" file

命令:snmpwalk   snmp查看OID值
    snmpwalk -v 3 -l authPriv -u p2eAy1H0MqSbGT6oDuzXC3Wv -a MD5 -A oRghaPqum1v7ZCHKxFey3sl5 -x DES -X oRghaPqum1v7ZCHKxFey3sl5 124.92.253.45 1.3.6.1.4.1.2011.20.3.12.1.6.0
    snmpwalk -v 2 -c public 125.88.105.225:161 1.3.6.1.2.1.2.2.1.5

命令:tar   打包,解压
        c 表示创建; x 表示解压; t 表示查看;
        tar -zcvf mytar.tar.gz myfiles/*                打包并压缩
        tar -zcvf mytar.tar.gz -X temp_file myfiles/*   打包(不包括temp_file文件中列出的文件)并压缩
        tar -zcvf mytar.tar.gz --exclude=abc/* .        打包当前目录下所有文件(但不包括abc目录下的文件)
        tar -zxvf mytar.tar.gz -T temp_file             temp_file文件中包含的文件都会被解压
        tar -zxvf mytar.tar.gz filename1 filename2 ...  解压mytar.tar.gz 包中指定的文件:filename1,filename2...

        tar -C/home/zcj -zcvf mem_check.tar.gz mem_check/        将当前目录下的mem_check打包,并将包保存到/home/zcj目录下
        tar -C/home/zcj -zxvf mem_check.tar.gz          将当前目录下的mem_check.tar.gz包解压到/home/zcj目录下

命令:diff    比较差异
        diff -y file1 file2 [-W120]    并排(-y)显示比较结果,每行最多显示120个字符
                结果说明:  < 表示只位于file1中
                            > 表示只位于file2中
                            | 数据不一致
        其他参数: -i 忽略大小写;        -E 忽略tab键;         -b 忽略空格;        -B 忽略空行;     -w  忽略所有空格;
                -r 比较目录时,递归子目录
                -q 只显示文件是否一致
                -d  或者 --suppress-common-lines(与-y配合)  只显示差异行
    sdiff命令相当于 “diff -y”命令

命令:LVS      LVS相关命令
    crm_mon -i 3  隔3秒刷新一次
    ipvsadm       查看路由信息,正常情况下,只有在一个RRS中会看到路由信息
    ipvsadm -C    清楚当前路由信息
   增加路由(在有虚拟IP的机器中执行):ipvsadm -a -t <虚拟IP>:554 -r <实际ip> -g -w 5

    rcheartbeat start 启动
    在/etc/ha.d/ha.cf 文件中找ucast可以查看心跳IP

命令:VCS          VCS相关命令
    清除出错的资源:   hagrp -clear db2-rg -sys mdndb1
                其中,db2-rg 表示资源名;  mdndb1 表示节点名
    清除Admin_wait状态: hagrp -clearadminwait db2-rg -sys mdndb1
    强制启动节点:       hasys -force mdndb1

命令:ntp           时间同步配置
    通常情况下,在设置的初始,在5至10分钟有内6次交换。 一旦同步后,每10分钟与服务器时间进行一次同步
    1)配置/etc/ntp.conf文件,只要存在如下项就可以了:
        server 125.88.104.19 prefer
        server 125.88.104.20
        fudge 127.127.1.0 stratum 4       # 设置本地时间源的层数为4,最大为15层,16表示不可访问
        driftfile /var/lib/ntp/drift/ntp.drift
        logfile   /var/log/ntp
    2)chmod 666 /var/lib/ntp/drift/ntp.drift
    3)如果存在/etc/init.d/xntpd文件,则用此文件启动ntp服务: /etc/init.d/xntpd start
                否则, rcntp start 启动
    4)查询ntp状态: ntpq -p,结果类似如下:
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
*125.88.104.19   202.96.168.10    2 u   10   64  377    1.339    0.953   0.266
+125.88.104.20   202.96.168.10    2 u    6   64  377    1.311    0.155   0.440
        *       主ntp服务器地址
        +       备ntp服务器地址
        remote  表示上层ntp服务器的地址
        refid   表示上层ntp服务器引用的地址
        st      表示ntp服务器的层数
        t       取值含义:u: unicast or manycast client, b: broadcast or multicast client, l: local (reference clock), s: symmetric (peer),
                          A: manycast server, B: broadcast server, M: multicast server
        when    当when==poll时,系统将与NTP服务器进行同步
        poll    表示同步间隔(秒),一开始该值比较小(同步频率高),当时间趋于稳定后,该值会扩大(同步频率降低)
        reach   表示与NTP Server同步的次数(8进制)
        delay   延误值,表示与上层服务器联系所花的时间,单位毫秒(0.001秒)
        offset  偏差值,表示与上层服务器的时间偏长,单位毫秒
        jitter  抖动值,这是一个用来做统计的值.它统计了在特定个连续的连接数里offset的分布情况.简单地说这个数值的绝对值越小,主机的时间就越精确
   5)如果时间相差很大(超过1000秒),则需要先通过如下方式同步一下,否则ntp无法进行同步:
     /etc/init.d/xntpd stop  停止ntp服务
     ntpdate -b 125.88.104.19 强制同步

作者: compare2000    时间: 2014-05-24 00:01
命令:samba          samba配置
   1)在/etc/samba/smb.conf文件中增加:
   [home]
   comment = Home Directiories
   valid users = root
   path = /home

   read only = No
   2)smbpasswd修改密码
      如果提示“Failed to find entry for user root.”,则执行“smbpasswd -a root”,即,添加root用户并设置其密码
   3)rcsmb restart 重启服务

命令:CFS+2300            相关命令
    更换mount路径(从d盘更换为e盘)的步骤:(只需要在CFS集群中的任一台服务器中执行即可)
    1)卸载现有的d盘:  cfsumount /home/hms/data/d
    2)检查现有的mount点关联关系:  cfsmntadm display  结果类似如下:
  QY-BY1-S9-HMU1:~ # cfsmntadm display
  Cluster Configuration for Node: QY-BY1-S10-HMU2
  MOUNT POINT        TYPE      SHARED VOLUME     DISK GROUP       STATUS
  /home/hms/data/d Regular    dbvol            dbdg             MOUNTED
    3)删除现有的d盘: cfsmntadm delete /home/hms/data/d
    4)添加新mount点e盘: cfsmntadm add dbdg dbvol /home/hms/data/e
                          dbdg 指第二步中的“DISK GROUP”; dbvol指第二步中的“SHARED VOLUME”
    5)挂载新mount点: cfsmount /home/hms/data/e

命令:ifconfig         增加虚拟ip
   ifconfig eth6:1 192.168.3.49 netmask 255.255.255.0
   ifconfig  eth1 192.168.0.6 netmask 255.255.255.0 up
   ifconfig eth0 up
   ifconfig eth0 down

命令:ethtool   网卡信息查看
        ethtool eth0         查看网卡信息

命令:top       非交互式执行
   top -b -n 1        :-b 表示非交互式, -n 1 表示只显示一次
   top -p <pid>      只显示指定进程的数据

命令:xxd,od         十六进制查看
        xxd
        od

命令:mknod         建立块专用或字符专用文件   
mknod - 建立块专用或字符专用文件   
总览
mknod [options] name {bc} major minor
mknod [options] name p   
GNU 选项(缩写):
[-m mode] [--help] [--version] [--]   
描述
mknod 用指定名称产生一个FIFO(命名管道),字符专用或块专用文件。
文件系统中的一个专用文件存贮着三种信息(布朗型、整型、整型)。布朗型在字符文件与块文件之间作出选择,两个整型是主、次设备号。
通常,一个专用文件并不在磁盘上占用空间,仅仅是为操作系统提供交流,而不是为数据存贮服务。一般地,专用文件会指向一个硬件设备(如:磁盘、磁带、打印机、虚拟控制台)或者操作系统提供的服务(如:/dev/null, /dev/random)。
块文件通常类似于磁盘设备(在数据可以被访问的地方赋予一个块号,意味着同时设定了一个块缓存)。所有其他设备都是字符文件。(以前,两种文件类型间是有差别的。比如:字符文件I/O没有缓存,而块文件则有。)
mknod命令就是用来产生这种类型文件的。
以下参数指定了所产生文件的类型:
p    FIFO型
b    块文件
c    字符文件
GNU版本还允许使用u('unbufferd'非缓冲化),以保持与C语言的一致。
当创建一个块文件或字符文件时,主、次设备号必须在文件类型参数后给出。(十进制或八进制以0开头;GNU 版本还允许使用以0x开头的十六进制)缺省地,所产生的文件模式为0666('a+rw')。   
选项
-m mode, --mode=mode 为新建立的文件设定模式,就象应用命令chmod一样,以后仍然使用缺省模式建立新目录。
1   3   是主设备号1,次设备号3

linux中 /dev/null 被删除后恢复: mknod -m 666 /dev/null c 1 3

命令:watch     自动刷新某一命令的结果
    watch -n 2 -d "netstat -anp | grep  "121.11.155.42:554" | sort -k 3 -r"
    -n 2 表示每隔2秒刷新一次
    -d   表示每次刷新后,如果数据数据发生变化,则高亮显示差异
    双引号内表示每次刷新所执行的命令

命令:locale           locale配置
  locale -a         列出所有locale名称
  LC_ALL=zh_CN locale -ck LC_TIME        查询中文的日期时间格式

命令:  cat << EOF        cat命令
cat << 'E'OF   # 定界符以任何形式的引号(单引号,双引号都可以)括起来,则shell不会处理接下来的内容(主要是变量替换等)
hello $somebody
$(echo "hello"
EOF
结果打印:
hello $somebody
$(echo "hello"

命令:getconf         获取操作系统的参数
        getconf ARG_MAX    获取命令行参数的最大个数
        getconf LONG_BIT   获取系统位数

命令:cut
        cut -d, -f 1 test_file
    截取test_file文件中特定的列,
        -d    指定列分隔符
        -f    指定获取哪些列,可以指定多列(以逗号分隔),也可以指定列范围(以横杆表示)
              如果某一行没有分隔符,则不管指定获取哪些列,该行都会全部打印(除非指定了-s选项)
              如果某一行中没有指定的列(有分隔符),则该行打印空行

命令:bc            计算器
    bc命令中有四个特殊变量:
          scale         指定小数点后的位数,默认为0        
          ibase,obase   指定输入输出的进制,默认为10
          last          代表上次打印的值
    bc命令中的函数:
        length(expr)    返回expr表达式(或数字)的长度(不包括小数点)
        scale(expr)     返回expr表达式(或数字)小数点后的长度
   示例:
    echo "scale=2;2/3" |bc                返回: 0.66
    echo "scale(2.00500)" |bc             返回:5
    echo "length(2.00500)"|bc             返回:6

命令:expect            
    #!/usr/bin/expect -f
    set timeout 30                # 变量赋值
    spawn ssh root@12.33.44.55    # 执行某个命令,以便下面的expect命令等待结果
    expect "password:"            # 期望某个值的出现
    send "pppppp\r"               # 发送某个字符串
    expect "]*"
    send "ssh weiqiong@C\r"
    expect "password:"
    send "pppppp\r"
    interact                      # 可以进入交互界面
    if { $timeout == 2 } {        # if 语句,语句体的左{ 必须与if语句在同一行
    }
命令行方式:
        expect -c "set timeout 5; spawn -noecho ssh 121.10.175.10 echo; expect timeout { send_user \"\n\"; exit 1 } ; exit 0"
################
## 输入yes的expect脚本
#!/usr/bin/expect -f
set devip [lindex $argv 0]
set timeout 3
spawn ssh $devip "echo" > /dev/null
while 1 {
    expect {
        -nocase -re "(yes/no)" {
            send -- "yes\r"
            break
        }
        eof {
          break
        }
        timeout {
            exit 1
        }
    }
}

命令:perl
1,perl 超时判断
#!/usr/bin/perl
eval { # eval 相当于异常捕捉机制: eval块中的代码如有错误,或执行die语句,都会被eval捕捉,并保存在$@变量中
    local $SIG{ALRM} = sub { die "alarm" }; # 定义ALRM信号的处理函数
    alarm 2;                                  # 设置2秒超时
    `ssh 125.88.105.99 echo`;                 # 如果该命令超时,则执行$SIG{ALRM} 中定义的函数,并且退出当前eval子句
    alarm 0;        # 如果超时,则该语句不会被执行,直接执行eval块后面的语句
};
        # 如果超时,则变量$@的值为“alarm”
print "hello world";

2,perl 多线程
#!/usr/bin/perl
my $maxchild=20;
foreach $item (1..500) {
    while ( `ps -ef|grep $0|wc -l` > $maxchild) { select undef,undef,undef,0.1; };

    if ($PID=fork()){
        print "Starting Sub_ProcessPID\n";
    } else {
        print "I will handle dataitem\n";
        sleep 1;
        exit 1;
    };
}

命令:chpasswd            自动修改密码
    echo "user_name:new_passwd" | chpasswd

命令:grep         查找字符串
   选项:
        -c  计数
        -i  忽略大小写
        -n  显示行号
        -v  只显示不匹配的行
        -w  按单词搜索
   正则表达式:
        grep 中可以使用正则表达式,但不能使用\(\),\{\}
        egrep 新增正则表达式:
        +        匹配一个或多个前驱字符
        ?        匹配零个或一个前驱字符
        xxx|xxx  或
        ()       组字符

命令:sed           文本编辑
   选项:  -e   允许多次编辑
           -n   禁止默认输出
           -f   将sed命令放置到文件中,然后使用此选项导入该命令文件
           -i[subFix]   直接在文件中修改,如果指定了subFix,则同时生成后缀为subFix的备份文件。如,sed -ibak 's###g' file         

   命令:  d    删除行
           p    打印行
           r    读取一个文件的内容
           s    替换,s字符后遇到的第一个字符就是s命令的分隔符
                可以带如下标志:
                g    全局替换; p  打印行;      
           !    把命令应用到除了选出的行以外的其他所有行
   示例:
        sed -ibak '3r append.txt' file         # 读取append.
        只打印包含north的行:  sed -n '/north/p' file
        删除第3到最后一行:    sed -n '3,$d'    file                 # 如果不加-n选项的话,结果将会打印1,2两行的内容
        替换所有west为north:      sed -n 's/west/north/g'  file     # 如果不加g的话,则每行只替换第一个west
        找到某行,并替换其中的ip: sed  '/masterrelayip/s#[0-9\.]\{1,\}#$newip#' file    # $newip是一个变量
        每行最后两个数字后追加.5:  sed -n 's/[0-9][0-9]$/&.5/' file  # & 匹配前面找到的内容,可以用\&来匹配&字符
        在某一行之后添加内容:sed -i '/string_tobe_find/a\ string_tobe_appended' file
        在最后一行之后添加内容:sed -i '$ a\ string_tobe_appended' file
        在某一行之前添加内容:sed -i '/string_tobe_find/i\ string_tobe_appended' file
        寻找Hello ABC,并将ABC替换为World:
                         sed -n 's/\(Hello \)ABC/\1World/g'  file    # \1 匹配前面第一个\(\)中的内容,最多可以引用9个\(\)中的内容
        sed -ibak 's###g' file          直接在文件中修改,同时生成后缀为bak的备份文件
        sed '/SQL>/d;/^$/d; s/,/#/g; s/^[ ]*//;s/[ ]*%[ ]*/,/g' temp_mem_edsinfo.txt


命令:awk,nawk                 awk语言
   格式:  awk -F"[分隔符]" -v myvar1=$SHELL_VAR1 -v myvar2=$SHELL_VAR2 'BEGIN{} pattern {} END{}' datafile
   内置变量:
           ARGC            命令行中待处理文件的个数
           ARGIND          当前正在处理文件在命令行数组中的索引(从1开始)
           ARGV            命令行中待处理文件名的数组
           FILENAME        当前输入文件名
           FNR             当前文件的行号
           NR              当前已处理的行号,如果只处理一个文件则,NR等于FNR
           NF              当前记录域个数
           FS              输入域分隔符,默认为一个空格,可以通过-F选项指定
           OFS             输出域分隔符,默认为一个空格
           OFMT            数值输出格式(与printf中的格式一致,如:OFMT="%.2f";)
           RS              输入记录分隔符
           ORS             输出记录分隔符
           RSTART          match函数匹配的串偏移量
           RLENGTH         match函数匹配的串长度
           IGNORECASE      是否区分大小写,0表示区分;1表示不区分
   语句:
        if (条件) {}
        else if (条件) {}
        else {}
        while(条件) {}
        for (value in array){}
        for (i=0; i< 10 ;i++) {}
        循环中使用的:break    continue
        next 语句: 从输入文件中读取下一行,并从awk命令表顶部重新开始执行
        nextfile: 终止当前输入文件的处理,从下一个输入文件进行处理
        exit 语句: 终止awk程序
        delete arr[n]  删除数组的第n个元素
        delete arr     删除整个数组(有的awk不支持这种方式)
        split("",arr)  同样,也可以删除arr数组(当awk不支持delete arr时,可以使用这种方式)
        位运算:  and(v1,v2)    or(v1,v2)    lshift(val,count)    rshift(val,count)  xor(v1,v2)   compl(val)
   函数:  print    "info...."            
           printf  "格式说明符\n",变量1,变量2...                 # \n表示换行
                        格式说明符:  %c  字符; %s 字符串; %o 八进制值; %d 十进制值;%x  十六进制值;  
                                      %e  科学计数法的浮点数; %f 浮点数;  %g 自动选择%e或%f占用空间小的来显示
                        修饰符:  - 左对齐(默认右对齐)
                                  #整数在用八进制显示时有0前缀,十六进制显示时有0x前缀
                                  + 整数有符号显示(即正负号)
                                  0 显示的值用0补,而不是默认的空格
                  如: awk '{printf "%-15s,%20s", $1,$2}'    #  第一列左对齐,总宽15个字符;第二列右对齐,总宽20个字符
          var = sprintf("格式说明符\n",变量1,变量2..)       与printf类似,只是结果不是打印出来,而是可以保存到某个变量中
          sub (搜索表达式, 替换字符串 [, 目标字符串])  #  在“目标字符串”中,查找第一个“搜索表达式”,并替换为“替换字符串”
                                                       # 如果没有指定“目标字符串”,则“目标字符串”为$0
          gsub 与sub函数类似,只是会进行全局替换
          index(string, substring)        返回string字符串中substring开始的位置(从1开始计),未找到则返回0
          length(string)                  返回string字符串的长度
          substr(string, start [,length]) 返回string字符串 start开始,length长度的子字符串,如果未指定length,则返回start开始的所有字符串
          match(string, regularExpr)      在string字符串中匹配regularExpr,返回值为子字符串开始位置的索引,如果未找到,则返回0
                                          内置变量RSTART为子串的起始位置,RLENGTH未子串的长度
match($0,/[0-9\.]*/)
          split(string ,arrayName [,fieldSeparator])  返回数组长度。按照fieldSeparator来分解string字符串,结果保存到arrayName的数组中(该数组的序号从1开始计算)
                                                      默认分格符为:空
          int(expr)                       求整,直接把小数点后的内容去掉
          srand(expr)                     初始化rand()用
          rand()                          返回[0,1)之间的随机数,示例:awk -F"," 'BEGIN{srand(systime() );} {print rand();}'
          tolower(str)                    将字符串转换为小写
          toupper(str)                    将字符串转换为大写
          strtonum(str)                   将字符串转换为数字,如果字符串前为0x,则表示将十六进制字符串转换为数字
          strftime(format[,timestamp])    格式化时间,formart格式同date命令。timestamp表示自1970年1月1日以来经过的秒数,如未指定,则指当前时间。
          systime()                       返回自1970年1月1日以来经过的时间(按秒计算)
    自定义函数:
        awk '{ print "sum =",SquareSum($1,$2) }
        function SquareSum(x,y) { sum=x*x+y*y ; return sum } ' grade.txt
输出单引号
echo "a" |awk '{print "'\''" $1"'\''"}'
    echo "a" |awk '{print "\47"$1"\47"}'

grep plan * | grep name | awk -F":" '{file=$1;sub(".*name=\"","";sub("\".*",""; if (file != $0".plan"
{
    sub(".plan","",file);
    print "sed -ibak @/plan.*name/s#name=\"" $0 "\"#name=\"" file "\"#@ " file".plan";
} }' | tr '@' "'"

// 分析begin/end行的耗时,文件内容类似如下:
2012-11-01 11:00:01,187 DEBUG [T=937][com.huawei.iptv.om.app.icheck.ds.perfstat.AnalysisPerfPackage.analysisPerf() 199] entryataPackage [dn=NE=2100117, measUnitKey=。。。。
2012-11-01 11:00:01,187 DEBUG [T=937][com.huawei.iptv.om.app.icheck.ds.perfstat.AnalysisPerfPackage.analysisPerf() 222] exitataPackage [dn=NE=2100117, measUnitKey=。。。。
2012-11-01 11:00:01,187 DEBUG [T=937][com.huawei.iptv.om.app.icheck.ds.perfstat.AnalysisPerfPackage.analysisPerf() 199] entryataPackage [dn=NE=2100112, measUnitKey=。。。。
2012-11-01 11:00:01,188 DEBUG [T=937][com.huawei.iptv.om.app.icheck.ds.perfstat.AnalysisPerfPackage.analysisPerf() 222] exitataPackage [dn=NE=2100112, measUnitKey=。。。。
grep analysisPerf iptv_20121031200245427.log |awk '{print $1,$2,$6,$7}' |awk '
function time_diff(begin,end) {
    sub(",",":",begin);
    sub(",",":",end);
    split(begin,arrBegin,":";
    split(end,arrEnd,":";
    endMS = arrEnd[1] * 3600 * 1000 + arrEnd[2]*60*1000 + arrEnd[3]* 1000 + arrEnd[4] ;
    beginMS = arrBegin[1] * 3600 * 1000 + arrBegin[2]*60*1000 + arrBegin[3]* 1000 + arrBegin[4] ;
    return endMS - beginMS;
}
{
if ($3 == "exitataPackage"{
    print $3,$4,lastTime[$4], $2, time_diff(lastTime[$4],$2);
} else {
    lastTime[$4]=$2;
}
}'

命令:ps     查看某个进程的启动时间
        ps -A -ouid,pid,stime,etime,args
   输出格式:UID PID STIME ELAPSED COMMAND
          ELAPSED表示该进程从启动到当前的时间差,格式 dd-hh24:mi:ss

示例,获取mmserver进程的启动时间:

1,先获取进程运行时间,单位为秒:
elapse=$(ps -A -ouname,pid,stime,etime,args | grep mmserver | grep -v grep | awk 'NR==1{
    dd=0;time=$4;len = split($4,arr,"-";
    if (len > 1){    dd = arr[1];    time = arr[2];}
    split(time,arr,":";
    hour = arr[1];minute = arr[2];second = arr[3];
    print dd * 24 * 3600 + hour * 3600 + minute * 60 + second;
}')
2,再根据当前时间,就可以计算出该进程的启动时间了:
start_time=$(date -d "$elapse seconds ago" "+%F %X")

命令:ps 非终端登陆时,默认结果显示宽度为90,这样就导致CMD显示不全
        可以添加w选项,使得宽度增加。
        如果用ww选项,则宽度没有限制

命令:查看某个进程的线程信息: ps cu -Lp <pid>
USER       PID   LWP %CPU NLWP %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
i2kuser  24071 24071  0.0  502  7.7 6090640 1903356 ?     Sl   Aug27   0:00 java
i2kuser  24071 24072  0.0  502  7.7 6090640 1903356 ?     Sl   Aug27   0:01 java
i2kuser  24071 24073  0.0  502  7.7 6090640 1903356 ?     Sl   Aug27   0:20 java
......



命令:split   分解文件
   split -b 2m myfile                  # 将myfile文件分解为2m的大小

命令:md5sum
        md5sum file1 [file2 ...]  >  my_md5_files.md5          # 生成md5文件
        md5sum -c my_md5_files.md5                             # 校验md5文件

命令:dmidecode 识别单板
linux:/home # dmidecode | grep Product
        Product Name: Seabream
        Product Name: MLB1218   // 这是R2单板

R2PLUS-XXX:/home # dmidecode | grep Product
        Product Name: Tionesta
        Product Name: CN21HCSA  // 这是R2+单板

ATAE-QDSA:~ # dmidecode | grep Product
        Product Name: OSTA2.0
        Product Name: CN21QDSA  // 这是ATAE板载盘单板


命令:free

free -m
             total       used       free     shared    buffers     cached
Mem:          7991       7946         45          0        111       5404
-/+ buffers/cache:       2430       5560
Swap:         8197        200       7996
第一行:标题行
第二行:total (7991MB):总内存,表示物理总内存大小。
        used (7946MB):已用内存,表示总计分配给缓存(包含buffers 与cache )使用的数量,但其中可能部分缓存并未实际使用。
        free (45MB):未用内存,表示未分配的内存大小。
        shared(0):共享内存,一般系统不会用到
        buffers(111MB): 系统分配但未被使用的buffers 数量。
        cached(5404MB): 系统分配但未被使用的cache 数量
第三行:used(2430MB): 实际内存使用大小 = 第二行的(Used - buffers - cached)
       free(5560MB): 实际空闲内存大小 = 第二行的(free + buffers + cached)
第四行:total(8197MB):总交换空间大小
        used(200MB):   已经使用的交换空间大小
        free(7996MB):  未使用的交换空间大小

buffer与cache的区别:
   buffer是用来给块设备的缓存,主要记录文件系统的元数据,如目录里有什么,权限等。
   cache是用来给文件的缓存,主要记录文件的内容。


建立信任关系
假设需要在机器A中建立与机器B的信任关系,使得在机器A中不需要输入密码,就可以直接ssh到机器B:
1,在机器A中执行:“ ssh-keygen -t rsa ”(然后全部使用默认值)
   此时,将在~/.ssh/目录下生成两个文件:id_rsa;id_rsa.pub
2,将上一步生成的id_rsa.pub文件内容追加到机器B ~root/.ssh/authorized_keys文件中



/proc/sys/net/ipv4/conf/all/arp_ignore
arp_ignore - INTEGER Define different modes for sending replies in response to received ARP requests that resolve local target IP addresses: 0 - (default): reply for any local target IP address, configured on any interface
           1 - reply only if the target IP address is local address configured on the incoming interface
           2 - reply only if the target IP address is local address configured on the incoming interface and both with the sender's IP address are part from same subnet on this interface
           3 - do not reply for local addresses configured with scope host, only resolutions for global and link addresses are replied
           4-7 - reserved
           8 - do not reply for all local addresses The max value from conf/{all,interface}/arp_ignore is used when ARP request is received on the {interface}


tc命令
在bond0上配置流量整形:
tc qdisc add dev bond0 root tbf rate 2000mbit burst 2000kb limit 50mb
删除bond0上的流量整形:
tc qdisc del dev bond0 root
查询:
tc qdisc


rpm 命令

安装                 rpm -ivh  <rpm 包名>
卸载                 rpm -evh  <rpm 安装名>
查询                 rpm -q <rpm 安装名>    或者 rpm -qa 查询所有的安装包
查询包对应的安装文件  rpm -ql <rpm 安装名>

chkconfig命令
chkconfig [--add][--del][--list][系统服务] 或 chkconfig [--level <等级代号>][系统服务][on/off/reset]


curl 查看某个网址的访问时间
curl -o /dev/null -sG -w %{time_connect}:%{time_starttransfer}:%{time_total} "http://125.88.66.53/graph_view.php?action=tree&tree_id=1&leaf_id=17"
        time_connect:        建立到服务器的 TCP 连接所用的时间
        time_starttransfer:  在发出请求之后,Web 服务器返回数据的第一个字节所用的时间
        time_total:          完成请求所用的时间

ping 命令
  以指定IP去ping设备:ping -I 172.23.10.66 172.23.2.138

patch 命令
   1、生成patch文件
        diff -Nuar include/top_graph_header.php_old include/top_graph_header.php > top_graph_header4iptv.patch
   2、使用patch命令安装补丁
        

blockdev命令
  blockdev --getsize /dev/raw/raw1
  获取裸设备的大小,返回值单位:块(512Byte)

raw命令
raw -qa 查看所有裸设备的绑定号,如:
MEM2:/dev/mapper # raw -qa
/dev/raw/raw1:        bound to major 253, minor 0
/dev/raw/raw2:        bound to major 253, minor 1
/dev/raw/raw3:        bound to major 253, minor 2
然后用ls -l 命令查看逻辑卷,就可以知道逻辑卷对应哪个裸设备,如:
MEM2:/dev/mapper # ls -l oramem_dg-lv_*
brw-rw---- 1 oracle oinstall 253,  0 Oct 13 16:22 oramem_dg-lv_systemtbs
brw-rw---- 1 oracle oinstall 253,  1 Oct 13 16:22 oramem_dg-lv_sysauxtbs
brw-rw---- 1 oracle oinstall 253,  2 Oct 13 16:22 oramem_dg-lv_usertbs

根据绑定号(253,0),就可以知道逻辑卷oramem_dg-lv_systemtbs对应裸设备/dev/raw/raw1


ftp非交互方式
ftp -vni $ftp_ip << EOF > temp_ftp_file
    user $ftp_user $ftp_pwd
    cd $ftp_path
    mkdir ${db_path}
    cd ${db_path}
    binary
    put ${task_item}.tgz
    bye
EOF



pure-ftp
  pure-ftp 服务器默认IP为主机名,这样会导致客户端登陆时提示“425 Sorry, invalid address given”
  因此,需要在ftp服务器中修改一下 /etc/xinetd.d/pure-ftpd文件,修改其中的server_args配置项(增加-H参数): server_args = -H

  然后,重启ftp: /etc/init.d/xinetd restart

screen命令
  screen -ls 查看所有的session
  screen -r sessionID  attach指定的session
  screen -d sessionID  detache指定的session
  在screen窗口中的命令:
      ctrl+a d  退出窗口(screen继续运行)
      exit      退出screen


strace
使用strace跟踪进程执行和文件写相关的系统调用
strace -e trace=file,write,writev,fcntl,lseek,pwrite,writev, -f -p <pid>

iostat

Device:            tps   Blk_read/s   Blk_wrtn/s   Blk_read   Blk_wrtn
sda               0.58         9.95        37.47    6737006   25377400
tps :每秒进程下发的IO读、写请求数量
Blk_read/s: 每秒读扇区数量(一扇区为512bytes)

更详细的io统计信息(-x选项) :  iostat -x -k -d 1
Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await  svctm  %util
sda               0.00  9915.00    1.00   90.00     4.00 34360.00   755.25    11.79  120.57   6.33  57.60
rrqm/s: 每秒对该设备的读请求被合并次数,文件系统会对读取同块(block)的请求进行合并
wrqm/s: 每秒对该设备的写请求被合并次数
r/s: 每秒完成的读次数
w/s: 每秒完成的写次数
rkB/s: 每秒读数据量(kB为单位)
wkB/s: 每秒写数据量(kB为单位)
avgrq-sz:平均每次IO操作的数据量(扇区数为单位)
avgqu-sz: 平均等待处理的IO请求队列长度
await: 平均每次IO请求等待时间(包括等待时间和处理时间,毫秒为单位)
svctm: 平均每次IO请求的处理时间(毫秒为单位)
%util: 采用周期内用于IO操作的时间比率,即IO队列非空的时间比率,util = (r/s+w/s) * (svctm/1000)

pidstat
cpu使用情况统计(-u)
使用-u选项,pidstat将显示各活动进程的cpu使用统计,执行"pidstat -u”与单独执行"pidstat”的效果一样。

内存使用情况统计(-r)
pidstat -r -p 13084 1
15:08:18          PID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
15:08:19        13084 133835.00      0.00 15720284 15716896  96.26  mmmm
•minflt/s: 每秒次缺页错误次数(minor page faults),次缺页错误次数意即虚拟内存地址映射成物理内存地址产生的page fault次数
•majflt/s: 每秒主缺页错误次数(major page faults),当虚拟内存地址映射成物理内存地址时,相应的page在swap中,这样的page fault为major page fault,一般在内存使用紧张时产生
•VSZ: 该进程使用的虚拟内存(以kB为单位)
•RSS: 该进程使用的物理内存(以kB为单位)
•%MEM: 该进程使用内存的百分比
•Command: 拉起进程对应的命令


netstat -nap  命令
显示数据类似如下:
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 10.137.13.121:35765     0.0.0.0:*               LISTEN      21914/java

主要字段解释:
Recv-Q      数据已经在本地(LocalAddress)接收缓冲,但是还没有recv().
Send-Q      对方(Foreign Address)没有收到的数据或者说没有Ack的,还是本地缓冲区.

CLOSE_WAIT

    对方主动关闭连接或者网络异常导致连接中断,这时我方的状态会变成CLOSE_WAIT 此时我方要调用close()来使得连接正确关闭

TIME_WAIT

    我方主动调用close()断开连接,收到对方确认后状态变为TIME_WAIT。TCP协议规定TIME_WAIT状态会一直持续2MSL(即两倍的分段最大生存期),以此来确保旧的连接状态不会对新连接产生影响。处于TIME_WAIT状态的连接占用的资源不会被内核释放,所以作为服务器,在可能的情况下,尽量不要主动断开连接,以减少TIME_WAIT状态造成的资源浪费。

    目前有一种避免TIME_WAIT资源浪费的方法,就是关闭socket的LINGER选项。但这种做法是TCP协议不推荐使用的,在某些情况下这个操作可能会带来错误。

/proc/sys/net/ipv4/tcp_fin_timeout
如 果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2 状态的时间。对端可以出错并永远不关闭连接,甚至意外当机。缺省值是60 秒。2.2 内核的通常值是180 秒,你可以按这个设置,但要记住的是,即使你的机器是一个轻载的WEB 服务器,也有因为大量的死套接字而内存溢出的风险,FIN- WAIT-2 的危险性比FIN-WAIT-1 要小,因为它最多只能吃掉1.5K 内存,但是它们的生存期长些。参见tcp_max_orphans



/proc/sys/net/ipv4/tcp_keepalive_time
当keepalive 起用的时候,TCP 发送keepalive 消息的频度。缺省是2 小时。
/proc/sys/net/ipv4/tcp_keepalive_intvl
当探测没有确认时,重新发送探测的频度。缺省是75 秒。
/proc/sys/net/ipv4/tcp_keepalive_probes
在认定连接失效之前,发送多少个TCP 的keepalive 探测包。缺省值是9 。这个值乘以tcp_keepalive_intvl 之后决定了,一个连接发送了keepalive 之后可以有多少时间没有回应。




dd 命令

模拟一个15G(30000000*512Byte)的文件(zcj_test):  dd if=/dev/zero of=zcj_test count=30000000

作者: compare2000    时间: 2014-05-24 00:08
系统运行状况相关的Shell命令:  1.  Linux的实时监测命令(watch):
    watch 是一个非常实用的命令,可以帮你实时监测一个命令的运行结果,省得一遍又一遍的手动运行。该命令最为常用的两个选项是-d和-n,其中-n表示间隔多少秒 执行一次"command",-d表示高亮发生变化的位置。下面列举几个在watch中常用的实时监视命令:
    /> watch -d -n 1 'who'   #每隔一秒执行一次who命令,以监视服务器当前用户登录的状况
    Every 1.0s: who       Sat Nov 12 12:37:18 2011
   
    stephen  tty1           2011-11-11 17:38 (:0)
    stephen  pts/0         2011-11-11 17:39 (:0.0)
    root       pts/1         2011-11-12 10:01 (192.168.149.1)
    root       pts/2         2011-11-12 11:41 (192.168.149.1)
    root       pts/3         2011-11-12 12:11 (192.168.149.1)
    stephen  pts/4         2011-11-12 12:22 (:0.0)
    此时通过其他Linux客户端工具以root的身份登录当前Linux服务器,再观察watch命令的运行变化。
    Every 1.0s: who       Sat Nov 12 12:41:09 2011
   
    stephen  tty1          2011-11-11 17:38 (:0)
    stephen  pts/0        2011-11-11 17:39 (:0.0)
    root       pts/1        2011-11-12 10:01 (192.168.149.1)
    root       pts/2        2011-11-12 11:41 (192.168.149.1)
    root       pts/3        2011-11-12 12:40 (192.168.149.1)
    stephen  pts/4        2011-11-12 12:22 (:0.0)
    root       pts/5        2011-11-12 12:41 (192.168.149.1)
    最后一行中被高亮的用户为新登录的root用户。此时按CTRL + C可以退出正在执行的watch监控进程。
   
    #watch可以同时运行多个命令,命令间用分号分隔。
    #以下命令监控磁盘的使用状况,以及当前目录下文件的变化状况,包括文件的新增、删除和文件修改日期的更新等。
    /> watch -d -n 1 'df -h; ls -l'
    Every 1.0s: df -h; ls -l     Sat Nov 12 12:55:00 2011
   
    Filesystem            Size  Used Avail Use% Mounted on
    /dev/sda1             5.8G  3.3G  2.2G  61% /
    tmpfs                 504M  420K  504M   1% /dev/shm
    total 20
    -rw-r--r--. 1 root root 10530 Nov 11 23:08 test.tar.bz2
    -rw-r--r--. 1 root root   183 Nov 11 08:02 users
    -rw-r--r--. 1 root root   279 Nov 11 08:45 users2
    此时通过另一个Linux控制台窗口,在watch监视的目录下,如/home/stephen/test,执行下面的命令
    /> touch aa         #在执行该命令之后,另一个执行watch命令的控制台将有如下变化
    Every 1.0s: df -h; ls -l                                Sat Nov 12 12:57:08 2011
   
    Filesystem            Size  Used Avail Use% Mounted on
    /dev/sda1             5.8G  3.3G  2.2G  61% /
    tmpfs                 504M  420K  504M   1% /dev/shm
    total 20
    -rw-r--r--. 1 root root        0 Nov 12 12:56 aa
    -rw-r--r--. 1 root root        0 Nov 12 10:02 datafile3
    -rw-r--r--. 1 root root 10530 Nov 11 23:08 test.tar.bz2
    -rw-r--r--. 1 root root     183 Nov 11 08:02 users
    -rw-r--r--. 1 root root     279 Nov 11 08:45 users2
    其中黄色高亮的部分,为touch aa命令执行之后watch输出的高亮变化部分。
   
    2.  查看当前系统内存使用状况(free):
    free命令有以下几个常用选项:

选项 说明
-b 以字节为单位显示数据。
-k 以千字节(KB)为单位显示数据(缺省值)。
-m 以兆(MB)为单位显示数据。
-s delay 该选项将使free持续不断的刷新,每次刷新之间的间隔为delay指定的秒数,如果含有小数点,将精确到毫秒,如0.5为500毫秒,1为一秒。

    free命令输出的表格中包含以下几列:

列名 说明
total 总计物理内存的大小。
used 已使用的内存数量。
free 可用的内存数量。
Shared 多个进程共享的内存总额。
Buffers/cached 磁盘缓存的大小。


    见以下具体示例和输出说明:
    /> free -k
                        total         used          free     shared    buffers     cached
    Mem:       1031320     671776     359544          0      88796     352564
    -/+ buffers/cache:      230416     800904
    Swap:        204792              0     204792
    对于free命令的输出,我们只需关注红色高亮的输出行和绿色高亮的输出行,见如下具体解释:
    红色输出行:该行是从操作系统的角度来看待输出数据的,used(671776)表示内核(Kernel)+Applications+buffers+cached。free(359544)表示系统还有多少内存可供使用。
    绿色输出行:该行则是从应用程序的角度来看输出数据的。其free = 操作系统used + buffers + cached,既:
    800904 = 359544 + 88796 + 352564
    /> free -m
                      total        used        free      shared    buffers     cached
    Mem:          1007         656        351            0         86            344
    -/+ buffers/cache:        225        782
    Swap:          199             0        199
    /> free -k -s 1.5  #以千字节(KB)为单位显示数据,同时每隔1.5刷新输出一次,直到按CTRL+C退出
                      total        used       free     shared    buffers     cached
    Mem:          1007         655        351          0           86        344
    -/+ buffers/cache:        224        782
    Swap:          199             0        199

                      total        used       free     shared    buffers     cached
    Mem:          1007         655        351          0           86        344
    -/+ buffers/cache:        224        782
    Swap:          199             0        199

    3.  CPU的实时监控工具(mpstat):
    该命令主要用于报告当前系统中所有CPU的实时运行状况。
    #该命令将每隔2秒输出一次CPU的当前运行状况信息,一共输出5次,如果没有第二个数字参数,mpstat将每隔两秒执行一次,直到按CTRL+C退出。
    /> mpstat 2 5  
    Linux 2.6.32-71.el6.i686 (Stephen-PC)   11/12/2011      _i686_  (1 CPU)

    04:03:00 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
    04:03:02 PM  all    0.00    0.00    0.50    0.00    0.00    0.00    0.00    0.00   99.50
    04:03:04 PM  all    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
    04:03:06 PM  all    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
    04:03:08 PM  all    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
    04:03:10 PM  all    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
    Average:       all    0.00    0.00    0.10    0.00    0.00    0.00    0.00    0.00   99.90

    第一行的末尾给出了当前系统中CPU的数量。后面的表格中则输出了系统当前CPU的使用状况,以下为每列的含义:

列名 说明
%user 在internal时间段里,用户态的CPU时间(%),不包含nice值为负进程  (usr/total)*100
%nice 在internal时间段里,nice值为负进程的CPU时间(%)   (nice/total)*100
%sys 在internal时间段里,内核时间(%)       (system/total)*100
%iowait 在internal时间段里,硬盘IO等待时间(%) (iowait/total)*100
%irq 在internal时间段里,硬中断时间(%)     (irq/total)*100
%soft 在internal时间段里,软中断时间(%)     (softirq/total)*100
%idle 在internal时间段里,CPU除去等待磁盘IO操作外的因为任何原因而空闲的时间闲置时间(%) (idle/total)*100

    计算公式:
    total_cur=user+system+nice+idle+iowait+irq+softirq
    total_pre=pre_user+ pre_system+ pre_nice+ pre_idle+ pre_iowait+ pre_irq+ pre_softirq
    user=user_cur – user_pre
    total=total_cur-total_pre
    其中_cur 表示当前值,_pre表示interval时间前的值。上表中的所有值可取到两位小数点。   

    /> mpstat -P ALL 2 3  #-P ALL表示打印所有CPU的数据,这里也可以打印指定编号的CPU数据,如-P 0(CPU的编号是0开始的)
    Linux 2.6.32-71.el6.i686 (Stephen-PC)   11/12/2011      _i686_  (1 CPU)

    04:12:54 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
    04:12:56 PM    all      0.00      0.00     0.50    0.00      0.00    0.00    0.00      0.00     99.50
    04:12:56 PM      0     0.00      0.00     0.50    0.00      0.00    0.00    0.00      0.00     99.50

    04:12:56 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
    04:12:58 PM    all     0.00      0.00     0.00    0.00      0.00    0.00    0.00      0.00    100.00
    04:12:58 PM     0     0.00      0.00     0.00    0.00      0.00    0.00    0.00      0.00    100.00

    04:12:58 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
    04:13:00 PM    all      0.00     0.00    0.00    0.00      0.00    0.00     0.00      0.00    100.00
    04:13:00 PM     0      0.00     0.00    0.00    0.00      0.00    0.00     0.00      0.00    100.00

    Average:       CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
    Average:         all      0.00     0.00    0.17    0.00      0.00    0.00     0.00      0.00     99.83
    Average:          0      0.00     0.00    0.17    0.00      0.00    0.00     0.00      0.00     99.83

    4.  虚拟内存的实时监控工具(vmstat):
    vmstat命令用来获得UNIX系统有关进程、虚存、页面交换空间及CPU活动的信息。这些信息反映了系统的负载情况。vmstat首次运行时显示自系统启动开始的各项统计信息,之后运行vmstat将显示自上次运行该命令以后的统计信息。用户可以通过指定统计的次数和时间来获得所需的统计信息。
    /> vmstat 1 3    #这是vmstat最为常用的方式,其含义为每隔1秒输出一条,一共输出3条后程序退出。
    procs  -----------memory----------   ---swap-- -----io---- --system-- -----cpu-----
     r  b   swpd      free      buff   cache   si   so     bi    bo     in   cs  us  sy id  wa st
     0  0        0 531760  67284 231212  108  0     0  260   111  148  1   5 86   8  0
     0  0        0 531752  67284 231212    0    0     0     0     33   57   0   1 99   0  0
     0  0        0 531752  67284 231212    0    0     0     0     40   73   0   0 100 0  0

    /> vmstat 1       #其含义为每隔1秒输出一条,直到按CTRL+C后退出。

    下面将给出输出表格中每一列的含义说明:
    有关进程的信息有:(procs)
    r:  在就绪状态等待的进程数。
    b: 在等待状态等待的进程数。   
    有关内存的信息有:(memory)
    swpd:  正在使用的swap大小,单位为KB。
    free:    空闲的内存空间。
    buff:    已使用的buff大小,对块设备的读写进行缓冲。
    cache: 已使用的cache大小,文件系统的cache。
    有关页面交换空间的信息有:(swap)
    si:  交换内存使用,由磁盘调入内存。
    so: 交换内存使用,由内存调入磁盘。  
    有关IO块设备的信息有:(io)
    bi:  从块设备读入的数据总量(读磁盘) (KB/s)
    bo: 写入到块设备的数据总理(写磁盘) (KB/s)   
    有关故障的信息有:(system)
    in: 在指定时间内的每秒中断次数。
    sy: 在指定时间内每秒系统调用次数。
    cs: 在指定时间内每秒上下文切换的次数。   
    有关CPU的信息有:(cpu)
    us:  在指定时间间隔内CPU在用户态的利用率。
    sy:  在指定时间间隔内CPU在核心态的利用率。
    id:  在指定时间间隔内CPU空闲时间比。
    wa: 在指定时间间隔内CPU因为等待I/O而空闲的时间比。   
    vmstat 可以用来确定一个系统的工作是受限于CPU还是受限于内存:如果CPU的sy和us值相加的百分比接近100%,或者运行队列(r)中等待的进程数总是不等于0,且经常大于4,同时id也经常小于40,则该系统受限于CPU;如果bi、bo的值总是不等于0,则该系统受限于内存。

    5.  设备IO负载的实时监控工具(iostat):
    iostat主要用于监控系统设备的IO负载情况,iostat首次运行时显示自系统启动开始的各项统计信息,之后运行iostat将显示自上次运行该命令以后的统计信息。用户可以通过指定统计的次数和时间来获得所需的统计信息。
    其中该命令中最为常用的使用方式如下:
    /> iostat -d 1 3    #仅显示设备的IO负载,其中每隔1秒刷新并输出结果一次,输出3次后iostat退出。
    Linux 2.6.32-71.el6.i686 (Stephen-PC)   11/16/2011      _i686_  (1 CPU)

    Device:            tps   Blk_read/s   Blk_wrtn/s   Blk_read   Blk_wrtn
    sda                 5.35       258.39        26.19     538210      54560

    Device:            tps   Blk_read/s   Blk_wrtn/s   Blk_read   Blk_wrtn
    sda                 0.00         0.00         0.00                  0          0

    Device:            tps   Blk_read/s   Blk_wrtn/s   Blk_read   Blk_wrtn
    sda                 0.00         0.00         0.00                  0          0

    Device:            tps   Blk_read/s   Blk_wrtn/s   Blk_read   Blk_wrtn
    sda                 0.00         0.00         0.00                  0          0
    /> iostat -d 1  #和上面的命令一样,也是每隔1秒刷新并输出一次,但是该命令将一直输出,直到按CTRL+C退出。
    下面将给出输出表格中每列的含义:

列名 说明
Blk_read/s 每秒块(扇区)读取的数量。
Blk_wrtn/s 每秒块(扇区)写入的数量。
Blk_read 总共块(扇区)读取的数量。
Blk_wrtn 总共块(扇区)写入的数量。

    iostat还有一个比较常用的选项-x,该选项将用于显示和io相关的扩展数据。
    /> iostat -dx 1 3
    Device:  rrqm/s wrqm/s  r/s   w/s  rsec/s wsec/s avgrq-sz avgqu-sz   await  svctm  %util
    sda            5.27   1.31 2.82 1.14 189.49  19.50    52.75     0.53     133.04  10.74   4.26

    Device:  rrqm/s wrqm/s  r/s   w/s  rsec/s wsec/s avgrq-sz avgqu-sz   await  svctm  %util
    sda            0.00   0.00 0.00 0.00   0.00   0.00        0.00     0.00         0.00   0.00   0.00

    Device:  rrqm/s wrqm/s  r/s   w/s  rsec/s wsec/s avgrq-sz avgqu-sz   await  svctm  %util
    sda            0.00   0.00 0.00 0.00   0.00   0.00        0.00     0.00         0.00   0.00   0.00
    还可以在命令行参数中指定要监控的设备名,如:
    /> iostat -dx sda 1 3   #指定监控的设备名称为sda,该命令的输出结果和上面命令完全相同。

    下面给出扩展选项输出的表格中每列的含义:

列名 说明
rrqm/s 队列中每秒钟合并的读请求数量
wrqm/s 队列中每秒钟合并的写请求数量
r/s 每秒钟完成的读请求数量
w/s 每秒钟完成的写请求数量
rsec/s 每秒钟读取的扇区数量
wsec/s 每秒钟写入的扇区数量
avgrq-sz 平均请求扇区的大小
avgqu-sz 平均请求队列的长度
await 平均每次请求的等待时间
util 设备的利用率

    下面是关键列的解释:
    util是设备的利用率。如果它接近100%,通常说明设备能力趋于饱和。
    await是平均每次请求的等待时间。这个时间包括了队列时间和服务时间,也就是说,一般情况下,await大于svctm,它们的差值越小,则说明队列时间越短,反之差值越大,队列时间越长,说明系统出了问题。
    avgqu-sz是平均请求队列的长度。毫无疑问,队列长度越短越好。                 

     6.  当前运行进程的实时监控工具(pidstat):
     pidstat主要用于监控全部或指定进程占用系统资源的情况,如CPU,内存、设备IO、任务切换、线程等。pidstat首次运行时显示自系统启动开始的各项统计信息,之后运行pidstat将显示自上次运行该命令以后的统计信息。用户可以通过指定统计的次数和时间来获得所需的统计信息。
    在正常的使用,通常都是通过在命令行选项中指定待监控的pid,之后再通过其他具体的参数来监控与该pid相关系统资源信息。

选项 说明
-l 显示该进程和CPU相关的信息(command列中可以显示命令的完整路径名和命令的参数)。
-d 显示该进程和设备IO相关的信息。
-r 显示该进程和内存相关的信息。
-w 显示该进程和任务时间片切换相关的信息。
-t 显示在该进程内正在运行的线程相关的信息。
-p 后面紧跟着带监控的进程id或ALL(表示所有进程),如不指定该选项,将监控当前系统正在运行的所有进程。

    #监控pid为1(init)的进程的CPU资源使用情况,其中每隔2秒刷新并输出一次,3次后程序退出。
    /> pidstat -p 1 2 3 -l
    07:18:58 AM       PID    %usr %system  %guest    %CPU   CPU  Command
    07:18:59 AM         1    0.00    0.00    0.00    0.00     0  /sbin/init
    07:19:00 AM         1    0.00    0.00    0.00    0.00     0  /sbin/init
    07:19:01 AM         1    0.00    0.00    0.00    0.00     0  /sbin/init
    Average:               1    0.00    0.00    0.00    0.00     -  /sbin/init
    %usr:      该进程在用户态的CPU使用率。
    %system:该进程在内核态(系统级)的CPU使用率。
    %CPU:     该进程的总CPU使用率,如果在SMP环境下,该值将除以CPU的数量,以表示每CPU的数据。
    CPU:         该进程所依附的CPU编号(0表示第一个CPU)。

    #监控pid为1(init)的进程的设备IO资源负载情况,其中每隔2秒刷新并输出一次,3次后程序退出。
    /> pidstat -p 1 2 3 -d   
    07:24:49 AM       PID   kB_rd/s   kB_wr/s kB_ccwr/s  Command
    07:24:51 AM         1      0.00      0.00      0.00  init
    07:24:53 AM         1      0.00      0.00      0.00  init
    07:24:55 AM         1      0.00      0.00      0.00  init
    Average:               1      0.00      0.00      0.00  init
    kB_rd/s:   该进程每秒的字节读取数量(KB)。
    kB_wr/s:   该进程每秒的字节写出数量(KB)。
    kB_ccwr/s: 该进程每秒取消磁盘写入的数量(KB)。

    #监控pid为1(init)的进程的内存使用情况,其中每隔2秒刷新并输出一次,3次后程序退出。
    /> pidstat -p 1 2 3 -r
    07:29:56 AM       PID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
    07:29:58 AM         1      0.00      0.00    2828   1368   0.13  init
    07:30:00 AM         1      0.00      0.00    2828   1368   0.13  init
    07:30:02 AM         1      0.00      0.00    2828   1368   0.13  init
    Average:               1      0.00      0.00    2828   1368   0.13  init
    %MEM:  该进程的内存使用百分比。

    #监控pid为1(init)的进程任务切换情况,其中每隔2秒刷新并输出一次,3次后程序退出。
    /> pidstat -p 1 2 3 -w
    07:32:15 AM       PID   cswch/s nvcswch/s  Command
    07:32:17 AM         1      0.00      0.00  init
    07:32:19 AM         1      0.00      0.00  init
    07:32:21 AM         1      0.00      0.00  init
    Average:            1      0.00      0.00  init
    cswch/s:    每秒任务主动(自愿的)切换上下文的次数。主动切换是指当某一任务处于阻塞等待时,将主动让出自己的CPU资源。
    nvcswch/s: 每秒任务被动(不自愿的)切换上下文的次数。被动切换是指CPU分配给某一任务的时间片已经用完,因此将强迫该进程让出CPU的执行权。

    #监控pid为1(init)的进程及其内部线程的内存(r选项)使用情况,其中每隔2秒刷新并输出一次,3次后程序退出。需要说明的是,如果-t选项后面不加任何其他选项,缺省监控的为CPU资源。结果中黄色高亮的部分表示进程和其内部线程是树状结构的显示方式。
    /> pidstat -p 1 2 3 -tr
    Linux 2.6.32-71.el6.i686 (Stephen-PC)   11/16/2011      _i686_  (1 CPU)

    07:37:04 AM      TGID       TID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
    07:37:06 AM         1         -      0.00      0.00        2828   1368      0.13  init
    07:37:06 AM         -         1      0.00      0.00        2828   1368      0.13  |__init

    07:37:06 AM      TGID       TID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
    07:37:08 AM         1         -      0.00      0.00        2828   1368      0.13  init
    07:37:08 AM         -         1      0.00      0.00        2828   1368      0.13  |__init

    07:37:08 AM      TGID       TID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
    07:37:10 AM         1         -      0.00      0.00        2828   1368      0.13  init
    07:37:10 AM         -         1      0.00      0.00        2828   1368      0.13  |__init

    Average:         TGID       TID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
    Average:            1         -      0.00      0.00        2828   1368      0.13  init
    Average:            -         1      0.00      0.00        2828   1368      0.13  |__init
    TGID: 线程组ID。
    TID: 线程ID。  

    以上监控不同资源的选项可以同时存在,这样就将在一次输出中输出多种资源的使用情况,如:pidstat -p 1 -dr。

    7.  报告磁盘空间使用状况(df):
    该命令最为常用的选项就是-h,该选项将智能的输出数据单位,以便使输出的结果更具可读性。
    /> df -h
    Filesystem             Size  Used   Avail Use% Mounted on
    /dev/sda1             5.8G  3.3G  2.2G  61%   /
    tmpfs                  504M  260K  504M   1%  /dev/shm

    8.  评估磁盘的使用状况(du):

选项 说明
-a 包括了所有的文件,而不只是目录。
-b 以字节为计算单位。
-k 以千字节(KB)为计算单位。
-m 以兆字节(MB)为计算单位。
-h 使输出的信息更易于阅读。
-s 只显示工作目录所占总空间。
--exclude=PATTERN 排除掉符合样式的文件,Pattern就是普通的Shell样式,?表示任何一个字符,*表示任意多个字符。
--max-depth=N 从当前目录算起,目录深度大于N的子目录将不被计算,该选项不能和s选项同时存在。



    #仅显示子一级目录的信息。
    /> du --max-depth=1 -h
    246M    ./stephen
    246M    .   
    /> du -sh ./*   #获取当前目录下所有子目录所占用的磁盘空间大小。
    352K    ./MemcachedTest
    132K    ./Test
    33M     ./thirdparty   
    #在当前目录下,排除目录名模式为Te*的子目录(./Test),输出其他子目录占用的磁盘空间大小。
    /> du --exclude=Te* -sh ./*  
    352K    ./MemcachedTest
    33M     ./thirdparty

作者: compare2000    时间: 2014-05-24 00:10
Linux Shell 命令行编辑  
前言
快速编辑 *nux 的初学者说最烦就是 Shell 的快捷键,也许这样再看全文会更有趣。

其实,命令行适应了,可能比图形界面更有效率。至少对我来说是这样,我现在一看见那些所谓的“慢 Linux /Unix 这类从内核得到整体架构,再到哪怕是最小的一个应用小软件都以文本来支撑的系统,没有娴熟的命令行技巧确实是玩不转的。希望本文能对你提高命令行使用效率有帮助。

但是由于Shell 的主流版本也有好几个,所以,本文所说的内容,可能和你的系统有出入,但是思想是一样的,在你自己的平台上摸索一下,你也会找到你的平台太下编辑命令行的技巧和规律。

另外, VIM, Emacs 的快捷键是相通的,所以,熟练使用 *nux 下的其他软件有很好的启示作用。

命令行的技巧除了本文提到的,还有很多,你可以自己慢慢积累,收集和体会。当然如果你经常需要输入很繁琐的命令,那么建议你自己写 function, alias 等技巧来实现。

我目前使用的系统配置

内核: Gentoo Linux -- 2005.1;
Shell 版本: Gnome -- 2.10.1; gnome-terminal -- 2.10.0;

现在就开始吧

一、自动补齐:
这个技巧很多人都应该会了,就是当输入命令,目录或者是文件名的时候按 charactor ,再按
按[Ctrl + r], [Ctrl + p], [Ctrl + n]
[Ctrl] 键的同时(reverse-i-search), 此时你尝试一下输入你以前输入过的命令,当你每输入一个字符的时候,终端都会滚动显示你的历史命令。当显示到你想找的合适的历史命令的时候,直接 [Ctrl + p] 或[Ctrl + r ] (接着输入[Ctrl + c]
Unix 初学者会习惯性地按

四、光标跳转快捷键:
为了方便大家记忆,加点英语助记语在后面
[Ctrl+ w] 向后删除一个字,用来对付刚刚输入的错误字很有用
[Ctrl+ l] 刷新屏幕(stty:终端相关命令)
[Ctrl+ u] 从光标当前位置删除所有字符至行首
[Ctrl+ k] 从光标当前位置删除所有字符至行尾
[Ctrl+ d] 删除光标当前位置的字符
[Ctrl+ y] 粘贴最后一个被删除的字

[Alt + l] lower case (current word)
[Alt+ u] upper case (current word)
[Alt+ d] 删除从光标当前位置,到当前字的结尾字符

六、配置提示;
如果你是用 gnome-teminal ,上面的 Gnome 的窗口快捷键有冲突。

那么你需要做如下配置:

在 "Edit" -] "Keyboard Shortcuts ..."打开 KDE 下应该怎么配 KDE 的朋友补充一下,我很久没用
2~echo
$echo "hello, world." [Enter]

我们先输入 [Ctrl + r]
我们试试找出历史命令[e],[c],[h]这三个键,这个历史命令大概已经找到了,[Enter]就会再一次执行这个命令,但我们现在来练习一下命令行的编辑。[Ctrl+ a]
echo "hello,world.",并且将光标定位到行首,此时,光标应该在 e 字符上高亮。
第四步: echo, 并且光标仍然在行首,终端显示为:

$ "hello,world."

"printf"

我们尝试一下用 printf 来替代 echo,输入 f 字符后面高亮。[Ctrl+ e]
光标跳转到命令行尾部。[Ctrl+ b]
光标后退一个字符,此时光标应处于后双引号 "\n"
[\][n] ,此时的终端显示应该为:

$ printf"hello, world.\n"

可以


KeyFunction
Ctrl-c         Kill foreground process
Ctrl-z          Suspend foreground process
Ctrl-d         Terminate input, or exitshell
Ctrl-s          Suspend output
Ctrl-q         Resume output
Ctrl-o         Discard output
Ctrl-l          Clearscreen
控制字符都是可以用(stty命令)更改的。
可以用stty -a看看终端配置。
每个程序员都应该知道的8个Linux命令
每个程序员,在职业生涯的某个时刻,总会发现自己需要知道一些Linux方面的知识。我并不是说你应该成为一个Linux专家,我的意思是,当面对linux命令行任务时,你应该能很熟练的完成。事实上,学会了下面8个命令,我基本上能完成任何需要完成的任务。

注意:下面的每个命令都有十分丰富的文档说明。这篇文章并不是来详尽的展示每个命令的各种功用的。我在这里要讲的是这几个最常用的命令的最常见用法。如果你对linux命令并不是很了解,你想找一些这方面的资料学习,那这篇文章将会给你一个基本的指导。



让我们从处理一些数据开始。假设我们有两个文件,分别记录的订单清单和订单处理结果。

123456789 order.out.log  8:22:19 111, 1, Patterns of Enterprise Architecture, Kindle edition, 39.99  8:23:45 112, 1, Joy of Clojure, Hardcover, 29.99  8:24:19 113, -1, Patterns of Enterprise Architecture, Kindle edition, 39.99  order.in.log  8:22:20 111, Order Complete  8:23:50 112, Order sent to fulfillment  8:24:20 113, Refund sent to processing


cat

cat – 连接文件,并输出结果

cat 命令非常的简单,你从下面的例子可以看到。

1234 jfields$ cat order.out.log 8:22:19 111, 1, Patterns of Enterprise Architecture, Kindle edition, 39.998:23:45 112, 1, Joy of Clojure, Hardcover, 29.998:24:19 113, -1, Patterns of Enterprise Architecture, Kindle edition, 39.99
就像它的说明描述的,你可以用它来连接多个文件。

1234567 jfields$ cat order.* 8:22:20 111, Order Complete8:23:50 112, Order sent to fulfillment8:24:20 113, Refund sent to processing8:22:19 111, 1, Patterns of Enterprise Architecture, Kindle edition, 39.998:23:45 112, 1, Joy of Clojure, Hardcover, 29.998:24:19 113, -1, Patterns of Enterprise Architecture, Kindle edition, 39.99
如果你想看这些log文件的内容,你可以把它们连接起来并输出到标准输出上,就是上面的例子展示的。这很有用,但输出的内容可以更有逻辑些。



sort

sort – 文件里的文字按行排序

此时sort命令显然是你最佳的选择。

1234567 jfields$ cat order.* | sort8:22:19 111, 1, Patterns of Enterprise Architecture, Kindle edition, 39.998:22:20 111, Order Complete8:23:45 112, 1, Joy of Clojure, Hardcover, 29.998:23:50 112, Order sent to fulfillment8:24:19 113, -1, Patterns of Enterprise Architecture, Kindle edition, 39.998:24:20 113, Refund sent to processing
就像上面例子显示的,文件里的数据已经经过排序。对于一些小文件,你可以读取整个文件来处理它们,然而,真正的log文件通常有大量的内容,你不能不考虑这个情况。此时你应该考虑过滤出某些内容,把cat、sort后的内容通过管道传递给过滤工具。



grep

grep, egrep, fgrep – 打印出匹配条件的文字行

假设我们只对Patterns of Enterprise Architecture这本书的订单感兴趣。使用grep,我们能限制只输出含有Patterns字符的订单。

123 jfields$ cat order.* | sort | grep Patterns8:22:19 111, 1, Patterns of Enterprise Architecture, Kindle edition, 39.998:24:19 113, -1, Patterns of Enterprise Architecture, Kindle edition, 39.99
假设退款订单113出了一些问题,你希望查看所有相关订单——你又需要使用grep了。

123 jfields$ cat order.* | sort | grep ":\d\d 113, "8:24:19 113, -1, Patterns of Enterprise Architecture, Kindle edition, 39.998:24:20 113, Refund sent to processing
你会发现在grep上的匹配模式除了“113”外还有一些其它的东西。这是因为113还可以匹配上书目或价格,加上额外的字符后,我们可以精确的搜索到我们想要的东西。

现在我们已经知道了退货的详细信息,我们还想知道日销售和退款总额。但我们只关心Patterns of Enterprise Architecture这本书的信息,而且只关心数量和价格。我现在要做到是切除我们不关心的任何信息。



cut

cut – 删除文件中字符行上的某些区域

又要使用grep,我们用grep过滤出我们想要的行。有了我们想要的行信息,我们就可以把它们切成小段,删除不需要的部分数据。

1234567 jfields$ cat order.* | sort | grep Patterns8:22:19 111, 1, Patterns of Enterprise Architecture, Kindle edition, 39.998:24:19 113, -1, Patterns of Enterprise Architecture, Kindle edition, 39.99 jfields$ cat order.* | sort | grep Patterns | cut -d"," -f2,5 1, 39.99 -1, 39.99
现在,我们把数据缩减为我们计算想要的形式,把这些数据粘贴到Excel里立刻就能得到结果了。

cut是用来消减信息、简化任务的,但对于输出内容,我们通常会有更复杂的形式。假设我们还需要知道订单的ID,这样可以用来关联相关的其他信息。我们用cut可以获得ID信息,但我们希望把ID放到行的最后,用单引号包上。



sed

sed – 一个流编辑器。它是用来在输入流上执行基本的文本变换。

下面的例子展示了如何用sed命令变换我们的文件行,之后我们在再用cut移除无用的信息。

123456789 jfields$ cat order.* | sort | grep Patterns \>| sed s/"[0-9\:]* \([0-9]*\)\, \(.*\)"/"\2, '\1'"/1, Patterns of Enterprise Architecture, Kindle edition, 39.99, '111'-1, Patterns of Enterprise Architecture, Kindle edition, 39.99, '113' lmp-jfields01:~ jfields$ cat order.* | sort | grep Patterns \>| sed s/"[0-9\:]* \([0-9]*\)\, \(.*\)"/"\2, '\1'"/ | cut -d"," -f1,4,51, 39.99, '111'-1, 39.99, '113'
我们对例子中使用的正则表达式多说几句,不过也没有什么复杂的。正则表达式做了下面几种事情

1、 删除时间戳

2、 捕捉订单号

3、 删除订单号后的逗号和空格

4、 捕捉余下的行信息

里面的引号和反斜杠有点乱,但使用命令行时必须要用到这些。

一旦捕捉到了我们想要的数据,我们可以使用 \1 & \2 来存储它们,并把它们输出成我们想要的格式。我们还在其中加入了要求的单引号,为了保持格式统一,我们还加入了逗号。最后,用cut命令把不必要的数据删除。

现在我们有麻烦了。我们上面已经演示了如何把log文件消减成更简洁的订单形式,但我们的财务部门需要知道订单里一共有哪些书。



uniq

uniq – 删除重复的行

下面的例子展示了如何过滤出跟书相关的交易,删除不需要的信息,获得一个不重复的信息。

123 jfields$ cat order.out.log | grep "\(Kindle\|Hardcover\)" | cut -d"," -f3 | sort | uniq -c   1  Joy of Clojure   2  Patterns of Enterprise Architecture
看起来这是一个很简单的任务。

这都是很好用的命令,但前提是你要能找到你想要的文件。有时候你会发现一些文件藏在很深的文件夹里,你根本不知道它们在哪。但如果你是知道你要寻找的文件的名字的话,这对你就不是个问题了。



find

find – 在文件目录中搜索文件

在上面的例子中我们处理了order.in.log和order.out.log这两个文件。这两个文件放在我的home目录里的。下面了例子将向大家展示如何在一个很深的目录结构里找到这样的文件。

123 jfields$ find /Users -name "order*"Users/jfields/order.in.logUsers/jfields/order.out.log
find命令有很多其它的参数,但99%的时间里我只需要这一个就够了。

简单的一行,你就能找到你想要的文件,然后你可以用cat查看它,用cut修剪它。但文件很小时,你用管道把它们输出到屏幕上是可以的,但当文件大到超出屏幕时,你也许应该用管道把它们输出给less命令。



less

less – 在文件里向前或向后移动

让我们再回到简单的 cat | sort 例子中来,下面的命令就是将经过合并、排序后的内容输出到less命令里。在 less 命令,使用“/”来执行向前搜索,使用“?”命令执行向后搜索。搜索条件是一个正则表达式。

1 jfields$ cat order* | sort | less
如果你在 less 命令里使用 /113.*,所有113订单的信息都会高亮。你也可以试试?.*112,所有跟订单112相关的时间戳都会高亮。最后你可以用 ‘q’ 来退出less命令。

linux里有很丰富的各种命令,有些是很难用的。然而,学会了前面说的这8个命令,你已经能处理大量的log分析任务了,完全不需要用脚本语言写程序来处理它们。

作者: compare2000    时间: 2014-05-24 00:10
linux目录架构
/       根目录
/bin         常用的命令   binary   file   的目錄
/boot       存放系统启动时必须读取的档案,包括核心   (kernel)   在内
          /boot/grub/menu.lst       GRUB设置
          /boot/vmlinuz       内核
          /boot/initrd           核心解壓縮所需   RAM   Disk
/dev         系统周边设备           
/etc         系统相关设定文件
          /etc/DIR_COLORS       设定颜色
          /etc/HOSTNAME       设定用户的节点名
          /etc/NETWORKING       只有YES标明网络存在
          /etc/host.conf   文件说明用户的系统如何查询节点名
          /etc/hosts   设定用户自已的IP与名字的对应表
          /etc/hosts.allow   设置允许使用inetd的机器使用   
          /etc/hosts.deny   设置不允许使用inetd的机器使用
          /etc/hosts.equiv   设置远端机不用密码
          /etc/inetd.conf   设定系统网络守护进程inetd的配置
          /etc/gateways   设定路由器
          /etc/protocols   设定系统支持的协议
          /etc/named.boot   设定本机为名字服务器的配置文件
          /etc/sysconfig/network-scripts/ifcfg-eth0       设置IP
          /etc/resolv.conf         设置DNS     
          /etc/X11     X   Window的配置文件,xorg.conf   或   XF86Config   這兩個   X   Server   的設定檔
          /etc/fstab         记录开机要mount的文件系统
          /etc/inittab   设定系统启动时init进程将把系统设置成什么样的runlevel
          /etc/issue   记录用户登录前显示的信息
          /etc/group   设定用户的组名与相关信息
          /etc/passwd   帐号信息
          /etc/shadow   密码信息
          /etc/sudoers   可以sudo命令的配置文件
          /etc/securetty   设定哪些终端可以让root登录
          /etc/login.defs   所有用户登录时的缺省配置
          /etc/exports   设定NFS系统用的
          /etc/init.d/       所有服務的預設啟動   script   都是放在這裡的,例如要啟動或者關閉
          /etc/xinetd.d/     這就是所謂的   super   daemon   管理的各項服務的設定檔目錄
          /etc/modprobe.conf       内核模块额外参数设定
          /etc/syslog.conf       日志设置文件
/home       使用者家目录
/lib         系统会使用到的函数库
          /lib/modules       kernel   的相关模块
          /var/lib/rpm       rpm套件安装处   
/lost+found         系統不正常產生錯誤時,會將一些遺失的片段放置於此目錄下
/mnt           外设的挂载点
/media       与/mnt类似
/opt           主机额外安装的软件
/proc         虚拟目录,是内存的映射
            /proc/version       内核版本
              /proc/sys/kernel       系统内核功能
/root         系统管理员的家目录
/sbin         系统管理员才能执行的指令
/srv           一些服務啟動之後,這些服務所需要取用的資料目錄
/tmp           一般使用者或者是正在執行的程序暫時放置檔案的地方
/usr           最大的目录,存许应用程序和文件
        /usr/X11R6:       X-Window目录   
        /usr/src:         Linux源代码
        /usr/include:系统头文件
        /usr/openwin   存放SUN的OpenWin   
        /usr/man   在线使用手册
        /usr/bin                       使用者可執行的   binary   file   的目錄
        /usr/local/bin           使用者可執行的   binary   file   的目錄
        /usr/lib                       系统会使用到的函数库
        /usr/local/lib           系统会使用到的函数库
        /usr/sbin                     系统管理员才能执行的指令
        /usr/local/sbin         系统管理员才能执行的指令
/var       日志文件
        /var/log/secure         記錄登入系統存取資料的檔案,例如   pop3,   ssh,   telnet,   ftp   等都會記錄在此檔案中
        /var/log/wtmp             記錄登入者的訊息資料,   last
        /var/log/messages     幾乎系統發生的錯誤訊息
        /var/log/boot.log     記錄開機或者是一些服務啟動的時候,所顯示的啟動或關閉訊息
        /var/log/maillog       紀錄郵件存取或往來(   sendmail   與   pop3   )的使用者記錄
        /var/log/cron             記錄   crontab   這個例行**的內容
        /var/log/httpd,   /var/log/news,   /var/log/mysqld.log,   /var/log/samba,   /var/log/procmail.log:
        分別是幾個不同的網路服務的記錄檔


一些常用的基本命令:
uname   -a         查看内核版本               
ls   -al         显示所有文件的属性
pwd                   显示当前路径                 
cd   -         返回上一次目录           cd   ~         返回主目录
date   s             设置时间、日期                     
cal             显示日历           cal   2006
bc                     计算器具                              
man     &   info           帮助手册
locale           显示当前字体           locale   -a         所有可用字体           /etc/sysconfig/i18n设置文件
LANG=en         使用英文字体                        
sync               将数据同步写入硬盘                 
shutdonw   -h   now   &   half   &   poweroff     关机
reboot           重启                                       
startx     &     init   5       进入图形介面
/work     &   ?work         向上、下查找文档内容
chgrp             改变档案群组     chgrp   testing   install.log         
chown           改变所属人       chown   root:root   install.log
chmod             改变属性           chmod   777   install.log           read=4     write=2     execute=1
cp       复制       cp   filename
rm       删除文件     rm   -rf   filename       强制删除文件
rmdir       删除文件夹
mv     移动         mv   123.txt   222.txt     重命名
mkdir           创建文件夹
touch           创建文件     更新当前时间
cat               由第一行开始显示           cat   |more     分页
nl                 在内容前加行号
more     &     less       一面一面翻动
head   -n   filename       显示第N行内容
tail   -n   filename     显示后N行内容
od                 显示非纯文档
df   -h   显示分区空间
du     显示目录或文件的大小
fdisk       分区设置         fdisk   -l   /dev/hda     显示硬盘分区状态
mkfs         建立各种文件系统     mkfs   -t   ext3     /dev/ram15      
fsck         检查和修复LINUX档案
ln             硬链接       ln   -s     软件链接
whereis       查找命令
locate         查找
find             查找       find   /   -name   "***.*** "
which           查看工具
whoami         显示当前用户
gcc   -v         查看GCC版本
chattr   +i   filename     禁止删除       chattr   -i   filename     取消禁止
lsattr         显示隐藏档属性
updatedb     更新资料库
mke2fs         格式化       mkfs   -t   ext3   
dd   if=/etc/passwd   of=/tmp/passwd.bak         备份
mount           列出系统所有的分区
mount   -t   iso9660   /dev/cdrom   /mnt/cdrom       挂载光盘
mount   -t   vfat   /dev/fd0   /mnt/floppy               挂载软盘
mount   -t   vfat   -o   iocharset=utf8,umask=000   /dev/hda2   /mnt/hda2       挂载fat32分区
mount   -t   ntfs   -o   nls=utf8,umask=000   /dev/hda3   /mnt/hda3                   挂载ntfs分区
Linux-NTFS   Project:   http://linux-ntfs.sourceforge.net/
umount   /mnt/hda3     缷载
ifconfig       显示或设置网络设备
service   network   restart       重启网卡     
ifdown   eth0     关闭网卡
ifup   eth0         开启网卡
clear         清屏
history         历史记录               !55     执行第55个指令
stty       设置终端         stty   -a
fdisk   /mbr       删除GRUB
at           僅進行一次的工作排程
crontab       循環執行的例行性命令         [e]编辑,[l]显示,[r]删除任务
&               后台运行程序         tar   -zxvf   123.tar.gz   &   ---------> 后台运行
jobs         观看后台暂停的程序       jobs   -l
fg             将后台程序调到前台       fg   n   ------> n是数字,可以指定进行那个程序
bg             让工作在后台运行
kill         结束进程         kill   -9   PID           [9]强制结束,[15]正常结束,[l]列出可用的kill信号
ps   aux     查看后台程序      
top           查看后台程序       top   -d   2         每两秒更新一次                 top   -d   2   -p10604       观看某个PID
                top   -b   -n   2   >   /tmp/top.txt   -----> 將   top   的資訊進行   2   次,然後將結果輸出到   /tmp/top.txt         
pstree       以树状图显示程序         [A]以   ASCII   來連接,   列出PID,   [p]列出帐号
killall       要刪除某個服務         killall   -9   httpd
free             显示内存状态           free   -m     --------> 以M为单位显示
uptime         显示目前系统开机时间
netstat       显示网络状态         netstat   -tulnp------> 找出目前系統上已在監聽的網路連線及其   PID
dmesg           显示开机信息         demsg   |   more
nice             设置优先权             nice   -n   -5   vi   &   -----> 用   root   給一個   nice   植為   -5   ,用於執行   vi   
renice         调整已存在优先权
runlevel     显示目前的runlevel
depmod         分析可载入模块的相依性
lsmod           显示已载入系统的模块
modinfo       显示kernel模块的信息
insmod         载入模块
modprobe       自动处理可载入模块
rmmod           删除模块
chkconfig       检查,设置系统的各种服务           chkconfig   --list   -----> 列出各项服务状态
ntsysv           设置系统的各种服务
cpio             备份文件

压缩命令:
  *.Z             compress   程式壓縮的檔案;   
  *.bz2         bzip2   程式壓縮的檔案;   
  *.gz           gzip   程式壓縮的檔案;   
打包:*.tar         tar   程式打包的資料,並沒有壓縮過;   
  *.tar.gz   tar   程式打包的檔案,其中並且經過   gzip   的壓縮
compress   filename     压缩文件     加[-d]解压     uncompress
gzip   filename       压缩     加[-d]解压     zcat   123.gz   查看压缩文件内容
bzip2   -z   filename     压缩     加[-d]解压       bzcat   filename.bz2     查看压缩文件内容

tar   -cvf   /home/123.tar   /etc     打包,不压缩
tar   -zcvf  /home/123.tar.gz   /etc     打包,不压缩
tar   -xvf   123.tar       解开包
tar   -zxvf   /home/123.tar.gz     以gzip解压
tar   -jxvf   /home/123.tar.bz2     以bzip2解压
tar   -ztvf   /tmp/etc.tar.gz       查看tar内容
cpio   -covB     >   [file|device]       份份
cpio   -icduv   <   [file|device]       还原

vi一般用法
一般模式                             编辑模式                                     指令模式
h   左                               a,i,r,o,A,I,R,O                           :w   保存
j   下                                 进入编辑模式                                 :w!   强制保存
k   上                                 dd   删除光标当前行                       :q!   不保存离开
l   右                                 ndd   删除n行                                   :wq!   保存后离开
0   移动到行首                 yy   复制当前行                                 :e!   还原原始档
$   移动到行尾                 nyy   复制n行                                     :w   filename   另存为
H   屏幕最上                     p,P   粘贴                                           :set   nu   设置行号
M   屏幕中央                     u     撤消                                             :set   nonu   取消行号
L   屏幕最下                     [Ctrl]+r   重做上一个动作               ZZ   保存离开
G   档案最后一行             [ctrl]+z   暂停退出                         :set   nohlsearch       永久地关闭高亮显示
/work   向下搜索                                                                       :sp   同时打开两个文档   
?work   向上搜索                                                                       [Ctrl]+w   两个文档设换
gg   移动到档案第一行                                                             :nohlsearch         暂时关闭高亮显示


认识SHELL
alias         显示当前所有的命令别名             alias   lm= "ls   -al "       命令别名         unalias   lm   取消命令别名
type             类似which
exprot         设置或显示环境变量
exprot   PATH= "$PATH ":/sbin     添加/sbin入PATH路径
echo   $PATH         显示PATH路径
bash             进入子程序
name=yang           设定变量
unset   name         取消变量
echo   $name         显示变量的内容
myname= "$name   its   me "       &       myname= '$name   its   me '           单引号时$name失去变量内容
ciw=/etc/sysconfig/network-scripts/           设置路径
env             列出所有环境变量
echo   $RANDOM         显示随意产生的数
set             设置SHELL
PS1= '[\u@\h   \w   \A   #\#]\$   '           提示字元的設定
      [root@linux   ~]#   read   [-pt]   variable           -----------读取键盘输入的变量
      參數:
      -p     :後面可以接提示字元!
      -t     :後面可以接等待的『秒數!』
declare         声明   shell   变量
ulimit   -a       显示所有限制资料
  ls   /tmp/yang   &&   echo   "exist "   ||   echo   "not   exist "
  意思是說,當   ls   /tmp/yang   執行後,若正確,就執行echo   "exist "   ,若有問題,就執行echo   "not   exist "   
  echo   $PATH   |   cut   -d   ': '   -f   5               以:为分隔符,读取第5段内容
  export   |   cut   -c   10-20             读取第10到20个字节的内容
  last   |   grep   'root '         搜索有root的一行,加[-v]反向搜索
  cat   /etc/passwd   |   sort         排序显示
  cat   /etc/passwd   |   wc             显示『行、字数、字节数』
正规表示法
[root@test   root]#   grep   [-acinv]   '搜尋字串 '   filename
              參數說明:
              -a   :將   binary   檔案以   text   檔案的方式搜尋資料
              -c   :計算找到   '搜尋字串 '   的次數
              -i   :忽略大小寫的不同,所以大小寫視為相同
              -n   :順便輸出行號
              -v   :反向選擇,亦即顯示出沒有   '搜尋字串 '   內容的那一行!
  grep   -n   'the '   123.txt           搜索the字符   -----------搜尋特定字串               
  grep   -n   't[ea]st '   123.txt         搜索test或taste两个字符---------利用   []   來搜尋集合字元
  grep   -n   '[^g]oo '   123.txt           搜索前面不为g的oo-----------向選擇   [^]   
  grep   -n   '[0-9] '   123.txt     搜索有0-9的数字
  grep   -n   '^the '   123.txt   搜索以the为行首-----------行首搜索^
  grep   -n   '^[^a-zA-Z] '   123.txt     搜索不以英文字母开头
  grep   -n   '[a-z]$ '   123.txt         搜索以a-z结尾的行----------   行尾搜索$
  grep   -n   'g..d '   123.txt           搜索开头g结尾d字符----------任意一個字元   .   
  grep   -n   'ooo* '   123.txt           搜索至少有两个oo的字符---------重複字元   *
sed         文本流编辑器         利用脚本命令来处理文本文件
awd         模式扫描和处理语言
  nl   123.txt   |   sed   '2,5d '       删除第二到第五行的内容
diff           比较文件的差异
cmp             比较两个文件是否有差异
patch         修补文件
pr               要打印的文件格式化


帐号管理
/etc/passwd         系统帐号信息
/etc/shadow         帐号密码信息         经MD5   32位加密
          在密码栏前面加『   *   』『   !   』禁止使用某帐号
/etc/group           系统群组信息
/etc/gshadow
newgrp         改变登陆组
useradd     &     adduser         建立新用户     --------->   useradd   -m   test     自动建立用户的登入目录
                    useradd   -m   -g   pgroup   test   ---------> 指定所属级
/etc/default/useradd       相关设定
/etc/login.defs               UID/GID   有關的設定
passwd         更改密码   ----------->   passwd   test
usermod       修改用户帐号
userdel       删除帐号   -----------> userdel   -r   test
chsh             更换登陆系统时使用的SHELL       [-l]显示可用的SHELL;[-s]修改自己的SHELL
chfn             改变finger指令显示的信息
finger         查找并显示用户信息
id                 显示用户的ID   ----------->     id   test
groupadd       添加组
groupmod       与usermod类似
groupdel       删除组
su   test         更改用户       su   -         进入root,且使用root的环境变量
sudo               以其他身份来执行指令
visudo           编辑/etc/sudoers             加入一行『   test   ALL=(ALL)   ALL   』
                      %wheel   ALL   =   (ALL)   ALL                               系统里所有wheel群组的用户都可用sudo
                      %wheel   ALL   =   (ALL)   NOPASSWD:   ALL           wheel群组所有用户都不用密码NOPASSWD
              User_Alias   ADMPW   =   vbird,   dmtsai,   vbird1,   vbird3                   加入ADMPW组
              ADMPW   ALL   =   NOPASSWD:   !/usr/bin/passwd,   /usr/bin/passwd   [A-Za-z]*,   \
              !/usr/bin/passwd   root             可以更改使用者密码,但不能更改root密码   (在指令前面加入   !   代表不可)
PAM   (Pluggable   Authentication   Modules,   嵌入式模組)
who   &   w           看谁在线                                          
last                 最近登陆主机的信息
lastlog           最近登入的時間         读取   /var/log/lastlog   
talk                 与其他用户交谈
write               发送信息         write   test       [ctrl]+d   发送
mesg                 设置终端机的写入权限         mesg   n   禁止接收           mesg   y   
wall                 向所有用户发送信息         wall   this   is   q   test
mail                 写mail      
/etc/default/useradd         家目录默认设置
quota             显示磁盘已使用的空间与限制           quota   -guvs   -----> 秀出目前   root   自己的   quota   限制值
                      quota   -vu       查询
quotacheck       检查磁盘的使用空间与限制           quotacheck   -avug     -----> 將所有的在   /etc/mtab   內,含有   quota   支援的   partition   進行掃瞄
                          [-m]   强制扫描     
          quota一定要是独立的分区,要有quota.user和quota.group两件文件,在/etc/fstab添加一句:
          /dev/hda3   /home   ext3   defaults,usrquota,grpquota   1   2
          chmod   600   quota*                   设置完成,重启生效
edquota         编辑用户或群组的quota     用户,[g]群组,[p]复制,[t]设置宽限期限   
                      edquota   -a   yang               edquota   -p   yang   -u   young   -----> 复制         
quotaon         开启磁盘空间限制           quotaon   -auvg   --------> 啟動所有的具有   quota   的   filesystem
quotaoff       关闭磁盘空间限制           quotaoff   -a     --------> 關閉了   quota   的限制
repquota   -av           查閱系統內所有的具有   quota   的   filesystem   的限值狀態
Quota   從開始準備   filesystem   的支援到整個設定結束的主要的步驟大概是:
1、設定   partition   的   filesystem   支援   quota   參數:
由於   quota   必須要讓   partition   上面的   filesystem   支援才行,一般來說,   支援度最好的是   ext2/ext3   ,
其他的   filesystem   類型鳥哥我是沒有試過啦!   啟動   filesystem   支援   quota   最簡單就是編輯   /etc/fstab   ,
使得準備要開放的   quota   磁碟可以支援   quota   囉;
2、建立   quota   記錄檔:
剛剛前面講過,整個   quota   進行磁碟限制值記錄的檔案是   aquota.user/aquota.group,   
要建立這兩個檔案就必須要先利用   quotacheck   掃瞄才行喔!
3、編輯   quota   限制值資料:
再來就是使用   edquota   來編輯每個使用者或群組的可使用空間囉;
4、重新掃瞄與啟動   quota   :
設定好   quota   之後,建議可以再進行一次   quotacheck   ,然後再以   quotaon   來啟動吧!

开机流程简介
1、載入   BIOS   的硬體資訊,並取得第一個開機裝置的代號;   
2、讀取第一個開機裝置的   MBR   的   boot   Loader   (亦即是   lilo,   grub,   spfdisk   等等)   的開機資訊;   
3、載入   Kernel   作業系統核心資訊,   Kernel   開始解壓縮,並且嘗試驅動所有硬體裝置;   
4、Kernel   執行   init   程式並取得   run-level   資訊;   
5、init   執行   /etc/rc.d/rc.sysinit   檔案;   
6、啟動核心的外掛模組   (/etc/modprobe.conf);   
7、init   執行   run-level   的各個批次檔(   Scripts   );   
8、init   執行   /etc/rc.d/rc.local   檔案;   
9、執行   /bin/login   程式,並等待使用者登入;   
10、登入之後開始以   Shell   控管主機。   
在/etc/rc.d/rc3.d內,以S开头的为开机启动,以K开头的为关闭,接着的数字代表执行顺序
GRUB   vga设定
彩度\解析度     640x480     800x600     1024x768     1280x1024       bit   
        256                 769             771             773               775             8   bit   
      32768               784             787             790               793           15   bit   
      65536               785             788             791               794           16   bit   
      16.8M               786             789             792               795           32   bit   

./configure         检查系统信息               ./configure   --help   |   more     帮助信息
make   clean           清除之前留下的文件
make                       编译
make   install       安装
rpm   -q     -----> 查询是否安装                           rpm   -ql   ------> 查询该套件所有的目录
rpm   -qi   -----> 查询套件的说明资料               rpm   -qc[d]   -----> 设定档与说明档
rpm   -ivh     ----> 安装                                         rpm   -V     --------> 查看套件有否更动过
rpm   -e     ------> 删除                                         rpm   -Uvh   -------> 升级安装     
--nodeps   -----> 强行安装                                 --test   -----> 测试安装

查找端口:
netstat -anp | grep 7161
udp        0      0 10.168.25.74:27161      0.0.0.0:*                           32543/oamagent
查找进程:
ps -ef | grep 32543
oamjyy   32543 32485  0 Jan29 ?        00:00:08 oamagent

date 01091414 设置时间为:1月9日14点14分
date 0109:   设置时间为:1点09分

作者: compare2000    时间: 2014-05-24 00:11
linux系统性能评估与优化  一、影响Linux服务器性能的因素

1. 操作系统级



&Oslash;       CPU

&Oslash;       内存

&Oslash;       磁盘I/O带宽

&Oslash;       网络I/O带宽



2.程序应用级



二、系统性能评估标准


影响性能因素
评判标准



糟糕

CPU
user% + sys%<70%
user% + sys%= 85%
user% + sys% >=90%

内存
Swap In  (si)=0

Swap Out(so)=0
Per CPU with 10 page/s
More Swap In & Swap Out

磁盘
iowait % < 20%
iowait % =35%
iowait % >= 50%




  

其中:

       %user:表示CPU处在用户模式下的时间百分比。

       %sys:表示CPU处在系统模式下的时间百分比。

       %iowait:表示CPU等待输入输出完成时间的百分比。

       swap in:即si,表示虚拟内存的页导入,即从SWAP DISK交换到RAM

       swap out:即so,表示虚拟内存的页导出,即从RAM交换到SWAP DISK。



三、系统性能分析工具



1.常用系统命令

Vmstat、sar、iostat、netstat、free、ps、top等



2.常用组合方式

• 用vmstat、sar、iostat检测是否是CPU瓶颈

• 用free、vmstat检测是否是内存瓶颈

• 用iostat检测是否是磁盘I/O瓶颈

• 用netstat检测是否是网络带宽瓶颈



四、Linux性能评估与优化



1. 系统整体性能评估(uptime命令)



[root@web1 ~]# uptime

16:38:00 up 118 days, 3:01, 5 users, load average: 1.22, 1.02, 0.91

这里需要注意的是:load average这个输出值,这三个值的大小一般不能大于系统CPU的个数,例如,本输出中系统有8个CPU,如果load average的三个值长期大于8时,说明CPU很繁忙,负载很高,可能会影响系统性能,但是偶尔大于8时,倒不用担心,一般不会影响系统性能。相反,如果load average的输出值小于CPU的个数,则表示CPU还有空闲的时间片,比如本例中的输出,CPU是非常空闲的。



1 cpu性能评估
Cpu是影响Linux性能的主要因素之一,下面先介绍几个查看CPU性能的命令。
1.1 vmstat命令
该命令可以显示关于系统各种资源之间相关性能的简要信息,这里我们主要用它来看CPU的一个负载情况。
下面是vmstat命令在某个系统的输出结果:
[root@node1 ~]# vmstat 2 3
procs -----------memory----------  ---swap--  -----io---- --system--  -----cpu------
r  b   swpd   free   buff  cache   si   so    bi    bo    in    cs    us sy  id  wa st
0  0    0    162240   8304  67032   0    0    13    21   1007   23     0  1  98  0  0
0  0    0    162240   8304  67032   0    0     1     0   1010   20     0  1  100 0  0
0  0    0    162240   8304  67032   0    0     1     1   1009   18     0  1  99  0  0
对上面每项的输出解释如下:
? procs
? r列表示运行和等待cpu时间片的进程数,这个值如果长期大于系统CPU的个数,说明CPU不足,需要增加CPU。
? b列表示在等待资源的进程数,比如正在等待I/O、或者内存交换等。
? memory
? swpd列表示切换到内存交换区的内存数量(以k为单位)。如果swpd的值不为0,或者比较大,只要si、so的值长期为0,这种情况下一般不用担心,不会影响系统性能。
? free列表示当前空闲的物理内存数量(以k为单位)
? buff列表示buffers cache的内存数量,一般对块设备的读写才需要缓冲。
? cache列表示page cached的内存数量,一般作为文件系统cached,频繁访问的文件都会被cached,如果cache值较大,说明cached的文件数较多,如果此时IO中bi比较小,说明文件系统效率比较好。
? swap
? si列表示由磁盘调入内存,也就是内存进入内存交换区的数量。
? so列表示由内存调入磁盘,也就是内存交换区进入内存的数量。
一般情况下,si、so的值都为0,如果si、so的值长期不为0,则表示系统内存不足。需要增加系统内存。
? IO项显示磁盘读写状况
? Bi列表示从块设备读入数据的总量(即读磁盘)(每秒kb)。
? Bo列表示写入到块设备的数据总量(即写磁盘)(每秒kb)
这里我们设置的bi+bo参考值为1000,如果超过1000,而且wa值较大,则表示系统磁盘IO有问题,应该考虑提高磁盘的读写性能。
? system 显示采集间隔内发生的中断数
? in列表示在某一时间间隔中观测到的每秒设备中断数。
? cs列表示每秒产生的上下文切换次数。
上面这2个值越大,会看到由内核消耗的CPU时间会越多。
? CPU项显示了CPU的使用状态,此列是我们关注的重点。
? us列显示了用户进程消耗的CPU 时间百分比。us的值比较高时,说明用户进程消耗的cpu时间多,但是如果长期大于50%,就需要考虑优化程序或算法。
? sy列显示了内核进程消耗的CPU时间百分比。Sy的值较高时,说明内核消耗的CPU资源很多。
根据经验,us+sy的参考值为80%,如果us+sy大于 80%说明可能存在CPU资源不足。
? id 列显示了CPU处在空闲状态的时间百分比。
? wa列显示了IO等待所占用的CPU时间百分比。wa值越高,说明IO等待越严重,根据经验,wa的参考值为20%,如果wa超过20%,说明IO等待严重,引起IO等待的原因可能是磁盘大量随机读写造成的,也可能是磁盘或者磁盘控制器的带宽瓶颈造成的(主要是块操作)。
综上所述,在对CPU的评估中,需要重点注意的是procs项r列的值和CPU项中us、sy和id列的值。


1.2  sar命令
检查CPU性能的第二个工具是sar,sar功能很强大,可以对系统的每个方面进行单独的统计,但是使用sar命令会增加系统开销,不过这些开销是可以评估的,对系统的统计结果不会有很大影响。
下面是sar命令对某个系统的CPU统计输出:
[root@webserver ~]# sar -u 3 5
Linux 2.6.9-42.ELsmp (webserver)        11/28/2008      _i686_  (8 CPU)

11:41:24 AM     CPU     %user     %nice   %system   %iowait    %steal     %idle
11:41:27 AM     all      0.88      0.00      0.29      0.00      0.00     98.83
11:41:30 AM     all      0.13      0.00      0.17      0.21      0.00     99.50
11:41:33 AM     all      0.04      0.00      0.04      0.00      0.00     99.92
11:41:36 AM     all      0.29      0.00      0.13      0.00      0.00     99.58
11:41:39 AM     all      0.38      0.00      0.17      0.04      0.00     99.41
Average:        all      0.34      0.00      0.16      0.05      0.00     99.45
对上面每项的输出解释如下:
? %user列显示了用户进程消耗的CPU 时间百分比。
? %nice列显示了运行正常进程所消耗的CPU 时间百分比。
? %system列显示了系统进程消耗的CPU时间百分比。
? %iowait列显示了IO等待所占用的CPU时间百分比
? %steal列显示了在内存相对紧张的环境下pagein强制对不同的页面进行的steal操作 。
? %idle列显示了CPU处在空闲状态的时间百分比。
这个输出是对系统整体CPU使用状况的统计,每项的输出都非常直观,并且最后一行Average是个汇总行,是上面统计信息的一个平均值。
需要注意的一点是:第一行的统计信息中包含了sar本身的统计消耗,所以%user列的值会偏高一点,不过,这不会对统计结果产生多大影响。
在一个多CPU的系统中,如果程序使用了单线程,会出现这么一个现象,CPU的整体使用率不高,但是系统应用却响应缓慢,这可能是由于程序使用单线程的原因,单线程只使用一个CPU,导致这个CPU占用率为100%,无法处理其它请求,而其它的CPU却闲置,这就导致 了整体CPU使用率不高,而应用缓慢 现象的发生 。
针对这个问题,可以对系统的每个CPU分开查询,统计每个CPU的使用情况:
[root@webserver ~]# sar -P 0 3 5
Linux 2.6.9-42.ELsmp (webserver)        11/29/2008      _i686_  (8 CPU)

06:29:33 PM     CPU     %user     %nice   %system   %iowait    %steal     %idle
06:29:36 PM       0      3.00      0.00      0.33      0.00      0.00     96.67
06:29:39 PM       0      0.67      0.00      0.33      0.00      0.00     99.00
06:29:42 PM       0      0.00      0.00      0.33      0.00      0.00     99.67
06:29:45 PM       0      0.67      0.00      0.33      0.00      0.00     99.00
06:29:48 PM       0      1.00      0.00      0.33      0.33      0.00     98.34
Average:          0      1.07      0.00      0.33      0.07      0.00     98.53
这个输出是对系统的第一颗CPU的信息统计,需要注意的是,sar中对CPU的计数是从0开始的,因此,“sar -P 0 3 5”表示对系统的第一颗CPU进行信息统计,“sar -P 4 3 5”则表示对系统的第五颗CPU进行统计。依次类推。可以看出,上面的系统有八颗CPU。


1.3 iostat命令
iostat指令主要用于统计磁盘IO状态,但是也能查看CPU的使用信息,它的局限性是只能显示系统所有CPU的平均信息,看下面的一个输出:
[root@webserver ~]# iostat  -c
Linux 2.6.9-42.ELsmp (webserver)        11/29/2008      _i686_  (8 CPU)

avg-cpu:  %user   %nice   %system  %iowait  %steal   %idle
           2.52    0.00    0.30     0.24     0.00    96.96
在这里,使用了“-c”参数,只显示系统CPU的统计信息,输出中每项代表的含义与sar命令的输出项完全相同,不再详述。


1.4 uptime

命令
uptime是监控系统性能最常用的一个命令,主要用来统计系统当前的运行状况,输出的信息依次为:系统现在的时间、系统从上次开机到现在运行了多长时间、系统目前有多少登陆用户、系统在一分钟内、五分钟内、十五分钟内的平均负载。看下面的一个输出:
[root@webserver ~]# uptime
18:52:11 up 27 days, 19:44,  2 users,  load average: 0.12, 0.08, 0.08
这里需要注意的是load average这个输出值,这三个值的大小一般不能大于系统CPU的个数,例如,本输出中系统有8个CPU,如果load average的三个值长期大于8时,说明CPU很繁忙,负载很高,可能会影响系统性能,但是偶尔大于8时,倒不用担心,一般不会影响系统性能。相反,如果load average的输出值小于CPU的个数,则表示CPU还有空闲的时间片,比如本例中的输出,CPU是非常空闲的。


1.5

本节小结
       上面介绍了检查CPU使用状况的四个命令,通过这些命令需要了解的是:系统CPU是否出现性能瓶颈,也就是说,以上这些命令只能查看CPU是否繁忙,负载是否过大,但是无法知道CPU为何负载过大,因而,判断系统CPU出现问题后,要结合top、ps等命令进一步检查是由那些进程导致CPU负载过大的。引起CPU资源紧缺的原因可能是应用程序不合理造成的,也可能是硬件资源匮乏引起的,所以,要具体问题具体分析,或者优化应用程序,或者增加系统CPU资源。


2 内存性能评估
      内存的管理和优化是系统性能优化的一个重要部分,内存资源的充足与否直接影响应用系统的使用性能,在进行内存优化之前,一定要熟悉linux的内存管理机制,这一点我们在前面的章节已经有深入讲述,本节的重点是如何通过系统命令监控linux系统的内存使用状况。
2.1 free 命令
free是监控linux内存使用状况最常用的指令,看下面的一个输出:
[root@webserver ~]# free -m
             total       used       free     shared    buffers     cached
Mem:          8111       7185        925          0        243       6299
-/+ buffers/cache:        643       7468
Swap:         8189          0       8189
  “free –m”表示以M为单位查看内存使用情况,在这个输出中,重点关注的应该是free列与cached列的输出值,由输出可知,此系统共8G内存,系统空闲内存还有925M,其中,Buffer Cache占用了243M,Page Cache占用了6299M,由此可知系统缓存了很多的文件和目录,而对于应用程序来说,可以使用的内存还有7468M,当然这个7468M包含了Buffer Cache和Page Cache的值。在swap项可以看出,交换分区还未使用。所以从应用的角度来说,此系统内存资源还非常充足。
    一般有这样一个经验公式:应用程序可用内存/系统物理内存>70%时,表示系统内存资源非常充足,不影响系统性能,应用程序可用内存/系统物理内存<20%时,表示系统内存资源紧缺,需要增加系统内存,20%<应用程序可用内存/系统物理内存<70%时,表示系统内存资源基本能满足应用需求,暂时不影响系统性能。
  free命令还可以适时的监控内存的使用状况,使用“-s”参数可以在指定的时间段内不间断的监控内存的使用情况:
[root@webserver ~]# free -b -s 5
total       used       free     shared    buffers     cached
Mem:    8505901056 7528706048  977195008          0  260112384 6601158656
-/+ buffers/cache:  667435008 7838466048
Swap:   8587149312     163840 8586985472

             total       used       free     shared    buffers     cached
Mem:    8505901056 7526936576  978964480          0  260128768 6601142272
-/+ buffers/cache:  665665536 7840235520
Swap:   8587149312     163840 8586985472

             total       used       free     shared    buffers     cached
Mem:    8505901056 7523987456  981913600          0  260141056 6601129984
-/+ buffers/cache:  662716416 7843184640
Swap:   8587149312     163840 8586985472
  其中,“-b”表示以千字节(也就是1024字节为单位)来显示内存使用情况。


2.2 通过watch与free相结合动态监控内存状况
watch是一个非常有用的命令,几乎每个linux发行版都带有这个工具,通过watch,可以动态的监控命令的运行结果,省去手动执行的麻烦。
  可以在watch后面跟上需要运行的命令,watch就会自动重复去运行这个命令,默认是2秒钟执行一次,并把执行的结果更新在屏幕上。例如:
[root@webserver ~]# watch -n 3 -d free
Every 3.0s: free                                   Sun Nov 30 16:23:20 2008

             total       used       free     shared    buffers     cached
Mem:       8306544    7349548     956996          0     203296    6500024
-/+ buffers/cache:     646228    7660316
Swap:      8385888        160    8385728
其中,“-n”指定重复执行的时间,“-d”表示高亮显示变动。


2.3 vmstat

命令监控内存
vmstat命令在监控系统内存方面功能强大,请看下面的一个输出:
procs  -----------memory----------  ---swap--  -----io---- --system--   ----cpu----
r  b   swpd    free buff    cache   si   so    bi    bo    in    cs    us sy id   wa
0  0  906440  22796 155616 1325496  340  180    2     4     1     4    80  0  10  10
0  0  906440  42796 155616 1325496  320  289    0    54    1095  287   70  15  0  15
0  0  906440  42884 155624 1325748  236  387    2   102    1064   276  78  2   5  15
对于内存的监控,在vmstat中重点关注的是swpd、si和so行,从这个输出可以看出,此系统内存资源紧缺,swpd占用了900M左右内存,si和so占用很大,而由于系统内存的紧缺,导致出现15%左右的系统等待,此时增加系统的内存是必须要做的。


2.4 sar -r命令组合
sar命令也可以监控linux的内存使用状况,可以通过“sar –r”组合查看系统内存和交换空间的使用率。请看下面的一个输出:
[root@webserver ~]# sar -r 2 3
Linux 2.6.9-42.ELsmp (webserver)        11/30/2008      _i686_  (8 CPU)

09:57:33 PM kbmemfree kbmemused  %memused kbbuffers  kbcached  kbcommit   %commit
09:57:35 PM    897988   7408556     89.19    249428   6496532    786556      4.71
09:57:37 PM    898564   7407980     89.18    249428   6496532    784276      4.70
09:57:39 PM    899196   7407348     89.17    249440   6496520    782132      4.69
Average:       898583   7407961     89.18    249432   6496528    784321      4.70
其中:
Kbmemfree表示空闲物理内存大小,kbmemused表示已使用的物理内存空间大小,%memused表示已使用内存占总内存大小的百分比,kbbuffers和kbcached分别表示Buffer Cache和Page Cache的大小,kbcommit和%commit分别表示应用程序当前使用的内存大小和使用百分比。
可以看出sar的输出其实与free的输出完全对应,不过sar更加人性化,不但给出了内存使用量,还给出了内存使用的百分比以及统计的平均值。从%commit项可知,此系统目前内存资源充足。


2.5  本节小结
上面介绍了内存监控常用的几个指令以及一些经验规则,其实现在的系统在内存方面出现的瓶颈已经很少,因为内存价格很低,充足的内存已经完全能满足应用程序和系统本身的需要,如果系统在内存方面出现瓶颈,很大的可能是应用程序本身的问题造成的。



3 磁盘I/O性能评估
在对磁盘I/O性能做评估之前,必须知道的几个方面是:
? 熟悉RAID存储方式,可以根据应用的不同,选择不同的RAID方式,例如,如果一个应用经常有大量的读操作,可以选择RAID5方式构建磁盘阵列存储数据,如果应用有大量的、频繁的写操作,可以选择raid0存取方式,如果应用对数据安全要求很高,同时对读写也有要求的话,可以考虑raid01存取方式等等。
? 尽可能用内存的读写代替直接磁盘I/O,使频繁访问的文件或数据放入内存中进行操作处理,因为内存读写操作比直接磁盘读写的效率要高千倍。
? 将经常进行读写的文件与长期不变的文件独立出来,分别放置到不同的磁盘设备上。
? 对于写操作频繁的数据,可以考虑使用裸设备代替文件系统。这里简要讲述下文件系统与裸设备的对比:
使用裸设备的优点有:
? 数据可以直接读写,不需要经过操作系统级的缓存,节省了内存资源,避免了内存资源争用。
? 避免了文件系统级的维护开销,比如文件系统需要维护超级块、I-node等。
? 避免了操作系统的cache预读功能,减少了I/O请求。
使用裸设备的缺点是:
? 数据管理、空间管理不灵活,需要很专业的人来操作。
其实裸设备的优点就是文件系统的缺点,反之也是如此,这就需要我们做出合理的规划和衡量,根据应用的需求,做出对应的策略。
下面接着介绍对磁盘IO的评估标准。
3.1 sar -d命令组合
通过“sar –d”组合,可以对系统的磁盘IO做一个基本的统计,请看下面的一个输出:
[root@webserver ~]# sar -d 2 3
Linux 2.6.9-42.ELsmp (webserver)        11/30/2008      _i686_  (8 CPU)

11:09:33 PM  DEV   tps   rd_sec/s wr_sec/s  avgrq-sz  avgqu-sz  await  svctm   %util
11:09:35 PM dev8-0  0.00  0.00     0.00      0.00      0.00      0.00   0.00    0.00

11:09:35 PM  DEV   tps  rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz  await   svctm   %util
11:09:37 PM dev8-0  1.00  0.00     12.00     12.00      0.00     0.00    0.00    0.00

11:09:37 PM   DEV   tps  rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz  await  svctm  %util
11:09:39 PM dev8-0  1.99   0.00    47.76     24.00     0.00      0.50    0.25    0.05

Average:  DEV     tps    rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz  await  svctm   %util
Average:  dev8-0  1.00   0.00      19.97     20.00      0.00     0.33    0.17    0.02
对上面每项的输出解释如下:
? DEV表示磁盘设备名称。
? tps表示每秒到物理磁盘的传送数,也就是每秒的I/O流量。一个传送就是一个I/O请求,多个逻辑请求可以被合并为一个物理I/O请求。
? rd_sec/s表示每秒从设备读取的扇区数(1扇区=512字节)。
? wr_sec/s表示每秒写入设备的扇区数目。
? avgrq-sz表示平均每次设备I/O操作的数据大小(以扇区为单位)。
? avgqu-sz表示平均I/O队列长度。
? await表示平均每次设备I/O操作的等待时间(以毫秒为单位)。
? svctm表示平均每次设备I/O操作的服务时间(以毫秒为单位)。
? %util表示一秒中有百分之几的时间用于I/O操作。
Linux中I/O请求系统与现实生活中超市购物排队系统有很多类似的地方,通过对超市购物排队系统的理解,可以很快掌握linux中I/O运行机制。比如:
avgrq-sz类似与超市排队中每人所买东西的多少。
avgqu-sz类似与超市排队中单位时间内平均排队的人数。
await类似与超市排队中每人的等待时间。
svctm类似与超市排队中收银员的收款速度。
%util类似与超市收银台前有人排队的时间比例。
对以磁盘IO性能,一般有如下评判标准:
正常情况下svctm应该是小于await值的,而svctm的大小和磁盘性能有关,CPU、内存的负荷也会对svctm值造成影响,过多的请求也会间接的导致svctm值的增加。
await值的大小一般取决与svctm的值和I/O队列长度以及I/O请求模式,如果svctm的值与await很接近,表示几乎没有I/O等待,磁盘性能很好,如果await的值远高于svctm的值,则表示I/O队列等待太长,系统上运行的应用程序将变慢,此时可以通过更换更快的硬盘来解决问题。
%util项的值也是衡量磁盘I/O的一个重要指标,如果%util接近100%,表示磁盘产生的I/O请求太多,I/O系统已经满负荷的在工作,该磁盘可能存在瓶颈。长期下去,势必影响系统的性能,可以通过优化程序或者通过更换更高、更快的磁盘来解决此问题。


3.2 iostat –d命令组合
通过“iostat –d”命令组合也可以查看系统磁盘的使用状况,请看如下输出:
[root@webserver ~]#   iostat -d 2 3
Linux 2.6.9-42.ELsmp (webserver)        12/01/2008      _i686_  (8 CPU)

Device:            tps   Blk_read/s   Blk_wrtn/s   Blk_read   Blk_wrtn
sda               1.87         2.58       114.12    6479462  286537372

Device:            tps   Blk_read/s   Blk_wrtn/s   Blk_read   Blk_wrtn
sda               0.00         0.00         0.00          0          0

Device:            tps   Blk_read/s   Blk_wrtn/s   Blk_read   Blk_wrtn
sda               1.00         0.00        12.00          0         24
对上面每项的输出解释如下:
? Blk_read/s表示每秒读取的数据块数。
? Blk_wrtn/s表示每秒写入的数据块数。
? Blk_read表示读取的所有块数
? Blk_wrtn表示写入的所有块数。
这里需要注意的一点是:上面输出的第一项是系统从启动以来到统计时的所有传输信息,从第二次输出的数据才代表在检测的时间段内系统的传输值。
可以通过Blk_read/s和Blk_wrtn/s的值对磁盘的读写性能有一个基本的了解,如果Blk_wrtn/s值很大,表示磁盘的写操作很频繁,可以考虑优化磁盘或者优化程序,如果Blk_read/s值很大,表示磁盘直接读取操作很多,可以将读取的数据放入内存中进行操作。对于这两个选项的值没有一个固定的大小,根据系统应用的不同,会有不同的值,但是有一个规则还是可以遵循的:长期的、超大的数据读写,肯定是不正常的,这种情况一定会影响系统性能。
“iostat –x”组合还提供了对每个磁盘的单独统计,如果不指定磁盘,默认是对所有磁盘进行统计,请看下面的一个输出:
[root@webserver ~]#   iostat -x /dev/sda  2 3
Linux 2.6.9-42.ELsmp (webserver)        12/01/2008      _i686_  (8 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           2.45    0.00    0.30    0.24    0.00   97.03

Device: rrqm/s  wrqm/s  r/s  w/s  rsec/s  wsec/s avgrq-sz avgqu-sz   await  svctm  %util
sda   0.01     12.48    0.10  1.78  2.58   114.03    62.33   0.07    38.39   1.30   0.24

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           3.97    0.00    1.83    8.19    0.00   86.14

Device:rrqm/s wrqm/s   r/s  w/s   rsec/s  wsec/s avgrq-sz avgqu-sz   await  svctm  %util
sda    0.00   195.00  0.00 18.00  0.00  1704.00    94.67     0.04    2.50   0.11   0.20

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           4.04    0.00    1.83    8.01    0.00   86.18

Device: rrqm/s  wrqm/s  r/s  w/s   rsec/s   wsec/s avgrq-sz avgqu-sz     await  svctm  %util
sda    0.00     4.50    0.00   7.00   0.00    92.00    13.14     0.01    0.79   0.14   0.10
这个输出基本与“sar –d”相同,需要说明的几个选项的含义为:
? rrqm/s表示每秒进行merged的读操作数目。
? wrqm/s表示每秒进行 merge 的写操作数目。
? r/s表示每秒完成读I/O设备的次数。
? w/s表示每秒完成写I/O设备的次数。
? rsec/s表示每秒读取的扇区数。
? wsec/s表示每秒写入的扇区数。


3.3 vmstat –d组合
通过“vmstat –d”组合也可以查看磁盘的统计数据,情况下面的一个输出:

[root@webserver ~]# vmstat -d 3 2|grep sda
disk- ------------reads------------ ------------writes----------- -----IO------
     total  merged sectors    ms    total    merged   sectors      ms     cur    sec
sda  239588 29282  6481862  1044442 4538678  32387680 295410812  186025580  0   6179
disk- ------------reads------------ ------------writes----------- -----IO------
     total  merged  sectors  ms    total     merged    sectors     ms     cur   sec
sda  239588 29282  6481862 1044442 4538680   32387690 295410908 186025581  0   6179
这个输出显示了磁盘的reads、writes和IO的使用状况。


3.4

本节小结
上面主要讲解了对磁盘I/O的性能评估,其实衡量磁盘I/O好坏是多方面的,有应用程序本身的,也有硬件设计上的,还有系统自身配置的问题等,要解决I/O的瓶颈,关键是要提高I/O子系统的执行效率。例如,首要要从应用程序上对磁盘读写进行优化,能够放到内存执行的操作,尽量不要放到磁盘,同时对磁盘存储方式进行合理规划,选择适合自己的RAID存取方式,最后,在系统级别上,可以选择适合自身应用的文件系统,必要时使用裸设备提高读写性能。



4 网络性能评估
网络性能的好坏直接影响应用程序对外提供服务的稳定性和可靠性,监控网络性能,可以从以下几个方面进行管理和优化。
4.1 通过ping命令检测网络的连通性
如果发现网络反应缓慢,或者连接中断,可以通过ping来测试网络的连通情况,请看下面的一个输出:
[root@webserver ~]# ping 10.10.1.254
PING 10.10.1.254 (10.10.1.254) 56(84) bytes of data.
64 bytes from 10.10.1.254: icmp_seq=0 ttl=64 time=0.235 ms
64 bytes from 10.10.1.254: icmp_seq=1 ttl=64 time=0.164 ms
64 bytes from 10.10.1.254: icmp_seq=2 ttl=64 time=0.210 ms
64 bytes from 10.10.1.254: icmp_seq=3 ttl=64 time=0.178 ms
64 bytes from 10.10.1.254: icmp_seq=4 ttl=64 time=0.525 ms
64 bytes from 10.10.1.254: icmp_seq=5 ttl=64 time=0.571 ms
64 bytes from 10.10.1.254: icmp_seq=6 ttl=64 time=0.220 ms
--- 10.10.1.254 ping statistics ---
7 packets transmitted, 7 received, 0% packet loss, time 6000ms
rtt min/avg/max/mdev = 0.164/0.300/0.571/0.159 ms, pipe 2
在这个输出中,time值显示了两台主机之间的网络延时情况,如果此值很大,则表示网络的延时很大,单位为毫秒。在这个输出的最后,是对上面输出信息的一个总结,packet loss表示网络的丢包率,此值越小,表示网络的质量越高。


4.2 通过netstat –i组合检测网络接口状况
netstat命令提供了网络接口的详细信息,请看下面的输出:
[root@webserver ~]# netstat -i
Kernel Interface table
Iface MTU  Met RX-OK     RX-ERR RX-DRP RX-OVR   TX-OK    TX-ERR TX-DRP TX-OVR       Flg
eth0  1500  0 1313129253  0      0       0     1320686497    0      0      0        BMRU
eth1  1500  0 494902025   0      0       0     292358810     0      0      0        BMRU
lo   16436  0 41901601    0      0       0     41901601      0      0      0        LRU
对上面每项的输出解释如下:
? Iface表示网络设备的接口名称。
? MTU表示最大传输单元,单位字节。
? RX-OK/TX-OK表示已经准确无误的接收/发送了多少数据包。
? RX-ERR/TX-ERR表示接收/发送数据包时产生了多少错误。
? RX-DRP/TX-DRP表示接收/发送数据包时丢弃了多少数据包。
? RX-OVR/TX-OVR表示由于误差而遗失了多少数据包。
? Flg表示接口标记,其中:
? L:表示该接口是个回环设备。
? B:表示设置了广播地址。
? M:表示接收所有数据包。
? R:表示接口正在运行。
? U:表示接口处于活动状态。
? O:表示在该接口上禁用arp。
? P:表示一个点到点的连接。
正常情况下,RX-ERR/TX-ERR、RX-DRP/TX-DRP和RX-OVR/TX-OVR的值都应该为0,如果这几个选项的值不为0,并且很大,那么网络质量肯定有问题,网络传输性能也一定会下降。
当网络传输存在问题是,可以检测网卡设备是否存在故障,如果可能,可以升级为千兆网卡或者光纤网络,还可以检查网络部署环境是否合理。


4.3 通过netstat –r组合检测系统的路由表信息
在网络不通,或者网络异常时,首先想到的就是检查系统的路由表信息,“netstat –r”的输出结果与route命令的输出完全相同,请看下面的一个实例:
[root@webserver ~]#  netstat -r
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
10.10.1.0       *               255.255.255.0   U         0   0       0  eth0
192.168.200.0   *               255.255.255.0   U         0   0       0  eth1
169.254.0.0     *               255.255.0.0     U         0   0       0  eth1
default         10.10.1.254     0.0.0.0         UG        0   0       0  eth0
关于输出中每项的具体含义,已经在前面章节进行过详细介绍,这里不再多讲,这里我们重点关注的是default行对应的值,default项表示系统的默认路由,对应的网络接口为eth0。


4.4 通过sar –n组合显示系统的网络运行状态
sar提供四种不同的选项来显示网络统计信息,通过“-n”选项可以指定4个不同类型的开关:DEV、EDEV、SOCK和FULL。DEV显示网络接口信息,EDEV显示关于网络错误的统计数据,SOCK显示套接字信息,FULL显示所有三个开关。请看下面的一个输出:
[root@webserver ~]# sar -n DEV 2 3
Linux 2.6.9-42.ELsmp (webserver)        12/01/2008      _i686_  (8 CPU)

02:22:31 PM     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s
02:22:33 PM        lo     31.34     31.34     37.53     37.53      0.00      0.00      0.00
02:22:33 PM      eth0    199.50    279.60     17.29    344.12      0.00      0.00      0.00
02:22:33 PM      eth1      5.47      4.98      7.03      0.36      0.00      0.00      0.00
02:22:33 PM      sit0      0.00      0.00      0.00      0.00      0.00      0.00      0.00

02:22:33 PM     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s
02:22:35 PM        lo     67.66     67.66     74.34     74.34      0.00      0.00      0.00
02:22:35 PM      eth0    159.70    222.39     19.74    217.16      0.00      0.00      0.00
02:22:35 PM      eth1      3.48      4.48      0.44      0.51      0.00      0.00      0.00
02:22:35 PM      sit0      0.00      0.00      0.00      0.00      0.00      0.00      0.00

02:22:35 PM     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s
02:22:37 PM        lo      4.52      4.52      9.25      9.25      0.00      0.00      0.00
02:22:37 PM      eth0    102.51    133.67     20.67    116.14      0.00      0.00      0.00
02:22:37 PM      eth1     27.14     67.34      2.42     89.26      0.00      0.00      0.00
02:22:37 PM      sit0      0.00      0.00      0.00      0.00      0.00      0.00      0.00

Average:        IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s
Average:           lo     34.61     34.61     40.48     40.48      0.00      0.00      0.00
Average:         eth0    154.08    212.15     19.23    226.17      0.00      0.00      0.00
Average:         eth1     11.98     25.46      3.30     29.85      0.00      0.00      0.00
Average:         sit0      0.00      0.00      0.00      0.00      0.00      0.00      0.00
对上面每项的输出解释如下:
? IFACE表示网络接口设备。
? rxpck/s表示每秒钟接收的数据包大小。
? txpck/s表示每秒钟发送的数据包大小。
? rxkB/s表示每秒钟接收的字节数。
? txkB/s表示每秒钟发送的字节数。
? rxcmp/s表示每秒钟接收的压缩数据包。
? txcmp/s表示每秒钟发送的压缩数据包。
? rxmcst/s表示每秒钟接收的多播数据包。
通过“sar –n”的输出,可以清楚的显示网络接口发送、接收数据的统计信息。此外还可以通过“sar -n EDEV 2 3”来统计网络错误信息等。


4.5 小结
      本节通过几个常用的网络命令介绍了对网络性能的评估,事实上,网络问题是简单而且容易处理的,只要我们根据上面给出的命令,一般都能迅速定位问题。解决问题的方法一般是增加网络带宽,或者优化网络部署环境。
      除了上面介绍的几个命令外,排查网络问题经常用到的命令还有traceroute,主要用于跟踪数据包的传输路径,还有nslookup命令,主要用于判断DNS解析信息。

作者: compare2000    时间: 2014-05-24 00:12
Linux 常用命令和技巧
liunx日常小技巧
改变文件或目录之读、写、执行之允许权
====================================
执行格式:chmod [-R] mode name ( name 可为文件名或目录名;mode可为 3 个 8 位元之数字,或利用ls -l 命令,列出文件或目录之读、写、执行允许权之文字缩写。)
mode : rwx rwx rwx r:read w:write x:execute(user group other 缩写为: u g o)
Example :
% chmod 755 dir1 将目录dir1,设定成任何使用者,皆有读取及执行之权利,但只有拥有者可做修改。
% chmod 700 file1 将文件file1,设定只有拥有者可以读、写和执行。
% chmod o+x file2 将文件file2,增加拥有者可以执行之权利。
% chmod g+x file3 将文件file3,增加群组使用者可执行之权利。
% chmod o-r file4 将文件file4,除去其它使用者可读取之权利。
改变文件或目录之拥有权
======================
执行格式:chown [-R] username name ( name 可为文件名或目录名。)
Example :
% chown user file1 将文件 file1 之拥有权,改为使用者 user 所有。
% chown -R user dir1 将目录 dir1,及其下所有文件和子目录之拥有权,改为使用者 user 所有。
检查自己所属之群组名称
======================
执行格式:groups
Example :
% groups
改变文件或目录之群组拥有权
==========================
执行格式:chgrp [-R] groupname name ( name 可为文件名或目录名 )
Example :
% chgrp vlsi file1 将文件 file1 之群组拥有权,改为 vlsi 群组。
% chgrp -R image dir1 将目录dir1,及其下所有文件和子目录,改为 image 群组。

改变文件或目录之最后修改时间(
变为当前时间)
=========================================
执行格式:touch name ( name 可为文件或目录名称。)
Example :
% touch file1
% touch dir1
文件之连结
=================
同一文件,可拥有一个以上之名称,可将文件做数个连结。
执行格式:ln oldname newname ( Hard link )
Example :
% ln file1 file2   将名称 file2,连结至文件 file1。
执行格式:ln -s oldname newname ( Symblick link )
Example :
% ln -s file3 file4 将名称 file4,连结至文件file3。
文件之字串找寻
==============
执行格式:grep string file
Example :
% grep abc file1 寻找文件file1中,列出字串 abc 所在之整行文字内容。
找寻文件或命令之路径
====================
执行格式:whereis command ( 显示命令之路径。)
执行格式:which command ( 显示命令之路径,及使用者所定义之别 )
执行格式:whatis command ( 显示命令功能之摘要。)
执行格式:find search-path -name filename -print ( 搜寻指定路径下,某文件之路径 )
Example :
% find / -name file1 -print ( 自根目录下,寻找文件名为 file1 之路径)
比较文件或目录之内容
====================
执行格式:diff [-r] name1 name2 ( name1 name2 可同时为文件名,或目录名称 )
Example :
% diff file1 file2 比较文件 file1 与 file2 内,各行之不同处。
% diff -r dir1 dir2 比较目录 dir1 与 dir2 内,各文件之不同处。
如何实现为一个网卡绑定多个IP地址?
====================================
  Linux的网络设备配置文件存放在/etc/sysconfig/network-scripts里面,对于以太网的第一个网络设备,配置文件名一般为 ifcfg-eth0 如果需要为第一个网络设备绑定多一个IP地址,只需要在/etc/sysconfig/network-scripts目录里面创建一个名为ifcfg- eth0:0的文件,内容样例为:
DEVICE="eth0:0"
IPADDR="211.100.10.119"
NETMASK="255.255.255.0"
ONBOOT="yes"
  其中的DEVICE为设备的名称,IPADDR为此设备的IP地址,NETMASK为子网掩码,ONBOOT表示在系统启动时自动启动。
  如果需要再绑定多一个IP地址,只需要把文件名和文件内的DEVICE中的eth0加一即可。LINUX最多可以支持255个IP别名。
如何设置login后欢迎信息
====================================
  修改/etc/motd,往里面写入文本,就能使用户通过telnet正确登录后执行shell之前得到相应的提示信息。
  motd就是“messages of the day”,也就是当日信息的意思。管理员可以往里面写一些需要注意的事项或者通知等来提醒正式用户。
如何设置login前欢迎界面
====================================
  修改/etc/issue或者issue.net,往里面写入文本,就能使得用户在login前得到相应的提示,这将有助于用户分辨自己正在连接哪里的主机。
  issue的内容是出现在本机登录的用户界面上,而issue.net则是在用户通过网络telnet的时候出现。
如何在bash下快速执行某个特定历史命令
====================================
在bash下面按ctrl+r可以查找历史命令中匹配的命令并执行
如何查看进程继承关系
====================================
直接在命令行中输入
pstree
即可,程序会以树状结构方式列出系统中正在运行的各进程之间的继承关系。
如何找出内存占用最大的进程
====================================
除了可以使用top命令查看内存使用情况之外,还可以使用更快的命令行命令,相关的命令如下:
ps aux | sort +4n
或者
ps aux | sort +5n
如何在Linux下面编辑二进制文件
====================================
http://freshmeat.net/redir/hexed ... xedit-1.2.3.src.tgz下载hexedit安装到系统上,就能用hexedit filename来编辑二进制文件。
如何设置用户密码过期时间
====================================
设置某个用户的密码过期时间可以用usermod -e来设置,如果要统一设置用户的密码过期时间,那么就要修改/etc/login.defs里面的PASS_MAX_DAYS,比如修改所有用户的密码过期时间是30天:
PASS_MAX_DAYS 30
如果这个值是99999,那么表示密码永不过期。
如何修改网卡MAC地址
====================================
首先必须关闭网卡设备,否则会报告系统忙,无法更改。
命令是: /sbin/ifconfig eth0 down
修改 MAC 地址,这一步较 Windows 中的修改要简单。
命令是:/sbin/ifconfig eth0 hw ether 00:AA:BB:CCD:EE
重新启用网卡 /sbin/ifconfig eht0 up
网卡的 MAC 地址更改就完成了
如何用Bash纠正错误命令
====================================
当你输入一个命令不知道是否正确的时候,可以使用ctrl+t来纠正到正确的命令。
比如输入mkdri,然后按ctrl+t,bash会帮你纠正到最接近的命令mkdir。
如何不显示其他用户的消息
====================================
用户可以使用mesg n来禁止别人给他发送信息,其实就是禁止别人往自己的终端上面的写权限。当别人试图再使用write给他发送信息时,发送者将会看见提示:
write: user has messages disabled on pts/n
如何知道某个命令使用了什么库文件
====================================
例如要知道ls使用了什么库文件,可以使用:
$ ldd /bin/ls
如何临时增加交换空间
====================================
产生一个64M的空文件
#dd if=/dev/zero of=/swapfile bs=1024 count=65536
初始化该文件为交换文件:
mkswap /swapfile 65536
sync
激活这个交换文件:
swapon /swapfile
如何使一个用户进程在用户退出系统后仍然运行
====================================
  使用nohup command &,比如:
  nohup wget -c ftp://test.com/test.iso
  这样即使用户退出系统,wget进程仍然继续运行直到test.iso下载完成为止
如何限制用户的最小密码长度
====================================
修改/etc/login.defs里面的PASS_MIN_LEN的值。比如限制用户最小密码长度是8:
PASS_MIN_LEN 8
这样用户设置密码的时候如果输入的密码长度小于8将不能设置
如何限制只有0组(gid=0)的用户可以su成root
====================================
修改/etc/login.defs里面的SU_WHEEL_ONLY的值为:
SU_WHEEL_ONLY yes
那么就只有gid为0的用户可以su成root。
如何禁用Ctrl+Alt+Del键重启系统
====================================
有时候为了防止误操作导致系统重新启动(如机房里面新来了个习惯用Window$系统的管理员),或者出于安全的原因,需要禁用Ctrl+Alt+Del组合键。
只需要注释掉/etc/inittab文件内的
ca::ctrlaltdel:/sbin/shutdown -t3 -r now
这一行即可。如何注释?在这一行的行首放一个“#”号即可。
怎么样格式化磁盘
====================================
Linux下有一条mkfs命令,相当于format,可以对磁盘进行文件系统制作操作(或者说格式化)。命令的语法为:
mkfs -t 文件系统类型 /dev/设备名
如要把/dev/sda1格式为reiserfs格式,使用以下命令:
mkfs -t reiserfs /dev/sda1
当然,也可以使用mkfs.fstype命令来格式化磁盘,其中的fstype为文件系统名,如reiserfs文件系统的格式化命令为mkfs.reiserfs。上面那条命令的相应版本为:
mkfs.reiserfs /dev/sda1
如何取消root命令历史记录以增加安全性
====================================
  为了设置系统不记录每个人执行过的命令,就在/etc/profile里设置:
  HISTFILESIZE=0
  HISTSIZE=0
  或者:
  ln -s /dev/null ~/.bash_history
  这样就可以了。
如何使用netrc文件进行自动ftp
====================================
在自己的home目录下建立一个权限600,名为.netrc的文件,内容是:
machine 192.168.0.1 login test password test
这样以后你每次ftp 192.168.0.1的时候,系统都会帮你以用户名test,密码test登录。
利用这个特征可以实现自动ftp。
例如你想要每天5:00到192.168.0.1机器上面获得/test目录下的文件test.txt,可以这么做:
建立一个文件ftp_cmd,内容为:
cd test
get test.txt
bye
然后使用crontab -e设置定时任务:
0 5 * * * ftp 192.168.0.1
如何测试硬盘性能
====================================
使用hdparm -t -T /dev/hdX就可以测试硬盘的buffer-cache reads和buffered disk reads两个数据,可以用来当作硬盘性能的参考。
同时使用hdparm -c3 /dev/hdaX还能设置硬盘以32bit传输,以加快数据传输的速度。
压缩和打包文件
====================================
.bz2 文件如何解压缩 用tar -I 或者bunzip2 命令都可以。
.bz2 — 使用bzip2命令压缩的文件,可以使用bzip2 -d filename 解包
.gz — 使用gzip命令压缩的文件,可以使用gunzip -d filename 解包
.tar — 使用tar打包的文件,即tarball文件,可以使用tar xf filename 解包
.tbz — 使用tar打完包后再以bzip2命令压缩的文件,可以使用tar jxf filename 解包
.tgz — 使用tar打完包后再以gzip命令压缩的文件,可以使用tar zxf filename 解包
个人总结:
[.tar] 解开:tar xvf *.tar 把文件打包不压缩:tar cvf *.tar DirName
[.tar.gz]解开:tar xzvf *.tar.gz 把文件打包并压缩:tar czvf *.tar.gz DirName
[.bz2] 解开:bzip2 -d *.bz2
[.bz] 解开:bzip -d *.bz
[.gz] 解开:gzip -d *.gz
如何对光驱、软驱实现AutoMount
====================================
安装完Redhat Linux之后,运行ntsysv,选中服务后台程序autofs.autofs的两个配置文件在/etc/auto.misc和 /etc/auto.master。
/etc/auto.master定义了mount目录和mount所需的配置文件名及其空闲时自动umount的时间。下面是默认配置:
[jephe@sh3 /etc]$ more auto.master
/misc /etc/auto.misc --timeout 60
下面是我的/etc/auto.misc配置文件:
[jephe@sh3 /etc]$ more auto.misc
cdrom -fstype=iso9660,ro :/dev/cdrom
floppy -fstype=vfat :/dev/fd0
e2floppy -fstype=ext2 :/dev/fd0
注:
a.确保/dev/cdrom作为符号链接指向你的光驱所在的分区,作为我的情况:
[jephe@sh3 /dev]$ ls cdrom -l
lrwxrwxrwx 1 root root 3 Aug 26 08:44 cdrom -> hdd
b.当软盘为windows 95格化化的vfat分区类型时,设定了安装目录/misc/floppy
c.当软盘为Linux格式化过的ext2分区类型时,设定了安装目录/misc/e2floppy
至此,若还未启动autofs,则运行/etc/rc.d/init.d/autofs start, 若对上述两个文件中做过任何更改,运行/etc/rc.d/init.d/autofs restart即可。
放入一片光盘进入光驱后,不需任何动作,直接进入目录/misc/cdrom,autofs 将自动先为你mount光驱同样,若放入一张vfat格式的软盘,你可以cd /misc/floppy,若是ext2格式的软盘,则cd /misc/e2floppy。在超过一分钟未使用它们,且当前目录不是自动mount目录/misc/cdrom,/misc/floppy, /misc/e2floppy,系统将自动umount它们。
cron 计划任务
====================================
简介
crontab-操作每个用户的守护程序和该执行的时间表。
部分参数说明
crontab file [-u user]-用指定的文件替代目前的crontab。
crontab-[-u user]-用标准输入替代目前的crontab.
crontab-1[user]-列出用户目前的crontab.
crontab-e[user]-编辑用户目前的crontab.
crontab-d[user]-删除用户目前的crontab.
crontab-c dir- 指定crontab的目录。
crontab文件的格式:M H D m d cmd.
M: 分钟(0-59)。
H:小时(0-23)。
D:天(1-31)。
m: 月(1-12)。
d: 一星期内的天(0~6,0为星期天)。
cmd要运行的程序,程序被送入sh执行,这个shell只有USER,HOME,SHELL这三个环境变量。
下面是一个例子文件:
#MIN HOUR DAY MONTH DAYOFWEEK COMMAND
#每天早上6点10分
10 6 * * * date
#每两个小时
0 */2 * * * date
#晚上11点到早上8点之间每两个小时,早上8点
0 23-7/2,8 * * * date
#每个月的4号和每个礼拜的礼拜一到礼拜三的早上11点
0 11 4 * mon-wed date
#1月份日早上4点
0 4 1 jan * date
范例
$crontab -l 列出用户目前的crontab.
#MIN HOUR DAY MONTH DAYOFWEEK COMMAND
10 6 * * * date
0 */2 * * * date
0 23-7/2,8 * * * date
如何在Linux里面恢复mbr?
====================================
  可以使用下面的命令来恢复:
  dd if=/boot/boot.NNNN of=/dev/hda bs=446 count=1
  其中bs(buffer size)是指重写的字节数。为什么不是512呢?主引导扇区是一个扇区(512字节)呀?答案是因为我们用上面的命令是为了修复可能被病毒修改了的主引导记录MBR,或者想把LILO卸载掉,而不是恢复整个主引导扇区。所以我们只把主引导扇区的备份文件boot.NNNN的前446个字节重写入主引导扇区。boot.NNNN是我们在安装Linux之前整个主引导分区的备份。如果我们把512个字节全部写入主引导扇区就可能会把安装了Linux后改变了的硬盘DPT表也**掉。那就坏事了。
如何列出一个目录占用的空间
====================================
du或du -s或du -k
du -S | sort -n 可以迅速发现那个目录是最大的。
用df可以看到已安装的文件系统的空间大小及剩余空间大小。
quota -v查看用户的磁盘空间信息,如果你用quota限制了用户空间大小的话。
在使用less 或 more时,如何直接启动编辑器
====================================
在less或者more中打入v键,会立即启动vi编辑器,可以对文件进行编辑
如何察看你当前使用的Linux内核的版本
====================================
  可以使用uname -r来查看当前内核版本。如果使用uname -a可以查看包括内核版本,机器硬件信息、网络节点名、操作系统名字等信息
如何显示文件的类型
====================================
  用命令 file 可以使你知道某个文件究竟是ELF格式的可执行文件, 还是shell script文件或是其他的什么格式 例如:
#file startx
如何将man page转成HTML格式
====================================
  使用 man2html 这个指令,就可以将 man page 转成 HTML 格式了。用法是:
  man2html filename > htmlfile.html
  例子:
  man2html /usr/man/man1/man2html.1 > ~/man2html.html
查看文件内容的特殊方法
====================================
相信最基本的cat和less你已经很熟悉了,如果有特殊的要求呢:
如果你只想看文件的前5行,可以使用head命令,如:
head -5 /etc/passwd
如果你想查看文件的后10行,可以使用tail命令,如:
tail -10 /etc/passwd
tail -f /var/log/messages
参数-f使tail不停地去读最新的内容,这样有实时监视的效果,当你在ppp拨号的过程中用它岂不是很方便!
查看文件中间一段,你可以使用sed命令,如:
sed -n '5,10p' /etc/passwd
这样你就可以只查看文件的第5行到第10行。
linux下文件分割与合并
====================================
  $dd if=source of=target.1 bs=1M count=10
  $dd if=source of=target.2 bs=1M skip=10
  source被分为target.1和target.2两个文件,其中target.1为source的前10M部分;target.2为source的减去10M后的部分。
  $cat target.1 target.2 > othersource
  source与othersource是同一个文件。
RedHat下如何显示彩色目录列表
====================================
  打开/etc/bashrc, 加入如下一行:
  alias ls="ls --color"
  下次启动bash时就可以像在Slackware里那样显示彩色的目录列表了, 其中颜色的含义如下:
蓝色-->目录
绿色-->可执行文件
红色-->压缩文件
浅蓝色-->链接文件
灰色-->其他文件
什么是Linux 运行级?
====================================
  什么是运行级呢?简单的说,运行级就是操作系统当前正在运行的功能级别。这个级别从1到6,具有不同的功能。这些级别在/etc/inittab文件里指定。这个文件是init程序寻找的主要文件,最先运行的服务是那些放在/etc/rc.d 目录下的文件。
  不同的运行级定义如下:(可以参考Red Hat Linux 里面的/etc/inittab)
  # 缺省的运行级,RHS 用到的级别如下:
  # 0 - 停机(千万不要把initdefault 设置为0 )
  # 1 - 单用户模式
  # 2 - 多用户,但是没有 NFS
  # 3 - 完全多用户模式
  # 4 - 没有用到
  # 5 - X11
  # 6 - 重新启动 (千万不要把initdefault 设置为6 )
  对各个运行级的详细解释:
  0 为停机,机器关闭。
  1 为单用户模式,就像Win9x 下的安全模式类似。
  2 为多用户模式,但是没有NFS 支持。
  3 为完整的多用户模式,是标准的运行级。
  4 一般不用,在一些特殊情况下可以用它来做一些事情。例如在笔记本电脑的电池用尽时,可以切换到这个模式来做一些设置。
  5 就是X11,进到Xwindow系统了。
  6 为重启,运行init 6机器就会重启。
  不同的运行级有不同的用处,也应该根据自己的不同情形来设置。例如,如果丢失了root 口令,那么可以让机器启动进入单用户状态来设置。在启动后的 lilo 提示符下输入:
  init=/bin/sh rw
  就可以使机器进入运行级1 ,并把root文件系统挂为读写。他会跳过所有系统认证,让你使用passwd 程序来改变root口令,然后启动到一个新的运行级。
系统平均负载(Load average)释疑
====================================
  在Linux系统中,uptime、w、top等命令都会有系统平均负载load average的输出,那么什么是系统平均负载呢?
  系统平均负载被定义为在特定时间间隔内运行队列中的平均进程树。如果一个进程满足以下条件则其就会位于运行队列中:
  - 它没有在等待I/O操作的结果
  - 它没有主动进入等待状态(也就是没有调用'wait')
  - 没有被停止(例如:等待终止)
  例如:
  [root@www2 init.d]# uptime
  7:51pm up 2 days, 5:43, 2 users, load average: 8.13, 5.90, 4.94
  命令输出的最后内容表示在过去的1、5、15分钟内运行队列中的平均进程数量。
  一般来说只要每个CPU的当前活动进程数不大于3那么系统的性能就是良好的,如果每个CPU的任务数大于5,那么就表示这台机器的性能有严重问题。对于上面的例子来说,假设系统有两个CPU,那么其每个CPU的当前任务数为:8.13/2=4.065。这表示该系统的性能是可以接受的。
如何以树状结构显示系统当前的任务
====================================
  运行ps时指定—forest选项,当前运行的进程将会以树状格式显示出来。比如:所有由xserver运行的程序,将会以xserver做为它们的“根”显示出来。
命令格式:
  ps afx
  其中’a’表示列出所有运行的进程;’x’列出所有的后台进程;’f’是’-forest’的缩写。输出的格式如下:
  329 ? SW 0:00 [wdm]
  342 ? R 9:57 \_ /usr/X11R6/bin/X :0 vt7 -fbbpp 16 -auth /usr/etc/
  343 ? SW 0:00 \_ [wdm]
  364 ? S 0:45 \_ /usr/bin/enlightenment
  399 ? S 0:00 \_ /usr/bin/ssh-agent sh /home/tuneup/.xse
  404 ? S 2:28 \_ gkrellm -wm
  787 ? S 0:05 \_ aterm -fg white -tr
  788 ttyp0 SW 0:00 \_ [bash]
  792 ttyp0 S 0:00 \_ vim todo
  注:另外一种得到格式化输出结果的方法是:pstree -pu
列目录时如何显示中文文件名
====================================
  我们可以使用--show-control-chars命令选项来显示。
  例如:
  $ls --show-control-chars
  如果使用的是一个比较常用的windows的分区,那么可以通过修改fstab来实现,在fstab中加入类似内容:
  /dev/hda1 /mnt/c vfat defaults,codepage=936,iocharset=cp936 0 0
  那么以后进入系统后都可以方便地使用/mnt/c访问windows的这个目录并正确显示目录名和文件名。
如何使用iso文件?
====================================
  许多Linux发行版本都有.iso的光盘镜像文件,可以用来刻录光盘。我们也可以在Linux系统下直接使用。
  #mount -t iso9660 -o loop xxxxx.iso /any/path/if/u/like
查询你的CPU等级
====================================
cat /proc/cpuinfo
如何使用户没有telnet和ftp权限
====================================
若只希望用户没telnet权限,则需要修改/etc/passwd中对应该用户的shell为/bin/true。
若只希望用户没有telnet和ftp权限,则需要修改/etc/passwd中对应该用户的shell为/bin/false。
如何连续执行一个命令
====================================
  使用watch命令,可以反复执行命令,如果和ls配合,可以达到观察某文件大小变化的效果。例如:
  $watch ls -l file.name
如何防止某个关键文件被修改
====================================
  在linux下,有些配置文件是不允许任何人包括root修改的,为了防止被误删除或修改,可以设定该文件的"不可修改位(immutable)"。
  例如:
  chattr +i /etc/fstab
  如果需要修改文件则:
  chattr -i /etc/fstab
  以后再修改文件。
linux环境下如何undelete
====================================
  先在自己的主目录下创建一个名为.trash的子目录,然后在bashrc加入以下指令:
  alias rm 'mv -f !* ~/.trash'
  alias undel 'mv ~/.trash/!* ./!*'
  alias cleantrash '/bin/rm -rf ~/.trash; mkdir ~/.trash;sync'
  alias lrm 'ls ~/.trash'
  若文档是直接用rm命令删除的,理论上 ext2 内 rm 掉的档案还是可以用 debugfs , ext2ed 救回来的.当然... 被 overwrite 掉就没救了。
如何找出磁盘中某个大小范围内的文件
====================================
  比如要查找磁盘中大于3M的文件:
  find . -size +3000k -exec ls -ld {} ;
如何快速重新执行已经执行过的命令
====================================
  使用!可以实现该功能,例如你前面执行了很多命令,现在突然想执行上一次执行的./configure命令,则只需要输入“!./con”即可而无需使用上下键来滚动查找。
  而!!则能代替前面一个命令。比如刚执行过一次ifconfig,输入“!!”则等于再执行一次ifconfig。
  而且这两个用法可以和其他命令组合,比如你刚执行过ifconfig,然后执行man !!,就等于执行man ifconfig。
当终端出现混乱时,如何让它恢复正常
====================================
  当使用stty命令而出现一些混乱或者更糟的是,使用一个程序而使终端设置完全混乱了时怎么办?要回到“现实”,试试下面的命令:
  stty sane
  如果击键变得混乱时,试着用来把命令括起来,输入的顺序是先按下CTRL再键入j键。
  ctty sane
  这个命令不会回到先前的设置,但却可以去除一些稀奇古怪的设置。而真正会出现什么设置要依赖于所使用的系统,但它至少会让你能输入字符,并见到结果。从这里开始,你可以把一些组合键设置为你所喜欢的方式。
如何将.gz文件分割为数个1.44mb
====================================
把一个文件分割到软盘:
tar cfvm /dev/fd0 file.tar.gz
把软盘上的文件合并到硬盘:
tar xvfm /dev/fd0
如何一次处理一整个目录
====================================
  Linux/UNIX 的很多常用命令如 rm , cp 等都有一个参数---- -r , 是递归的意思, 命令里加了参数 -r 就可以对目标目录及其下所有子目录进行操作,如:
  rm -rf /test (f 是 force 意为强行)
  该命令完全删除根目录下的子目录 test ,作用类似于 dos 下的 deltree ,当然使用这个命令时要特别小心。再如:
  cp -r /test /test1
  有类似 dos 下 xcopy /s 的作用。
redhat下如何允许root通过telnet登录?
====================================
方法1:/etc/securetty ( 加入 pts/0 、pts/1、...)
# echo "pts/0" >> /etc/securetty
方法2:为了在redhat linux系统中激活远程登陆,从文件/etc/pam.d/login中移去下面这一行:
auth required /lib/security/pam_security.so
将/etc/securetty这个文件改名就行啦,该文件是定义root只能在tty1~tty6的终端上登录的,详细的信息可以"man login"。
Linux正常重新启动的方法有很多种,下面介绍几种常用的重新启动方法:
====================================
Ctrl+Alt+Del
#init 6
#shutdown -r now
#reboot
为什么我的linux不允许普通用户登录?
====================================
  以root的身份登录系统,检查是不是有/etc/nologin这个文件,删除这个文件,然后再以普通用户的身份登录。相信问题已经解决。
  出现这种问题一般是因为系统在关闭的过程中意外中断了操作,如断线或者是插头被拔了之类的意外。而系统在关闭的过程中会自动的产生这个文件,以便通知用户系统正在关闭这就造成普通用户无法登录了。
  另外一种原因是系统管理员在对系统进行维护,为了维护的过程中不受其他用户的影响,需要生成这个文件来禁止其他用户登录。但很不幸系统管理维护完以后忘记删除这个文件了。
如何改变当前路径下所有目录和文件的所有权
====================================
改变所有子目录及文件的所有权
#chown -R owner[.group] *
也可以用find命令来实现:
#find . -exec chown owner[.group] {} ;
改变所有子目录及文件的属性
在你要改变属性的目录下,输入命令:
#chmod -R 777 *
就可以改变下面所有子目录及文件的属性,不过使用这个命令的时候要特别小心,要是在根目录下打入这个命令,你所有文件的属性都将改变,这就会引起很大的安全性问题。
如何快速查找文件
====================================
  查找文件可以用find,但最好是用locate,速度快,参数少。
  $locate filename
  它是在一个数据库里面查找,所以,要记得经常用updatedb命令更新数据库。一般地,在crontab中的cron.daily脚本会执行/usr/sbin/logrotate /etc/logrotate.conf命令,让机子在每天深夜更新数据库。
如何将Linux或FreeBSD复制到另一颗硬盘 ?
====================================
  Linux上的系统复制很简单,使用cp -ax将partition资料复制过去,重开机後设定lilo就可以了。
  FreeBSD也可以用cp来复制文档,但是对于复制整个文档系统并不是好方法。
  这里介绍使用dump和restore来做:
创建新的文档系统
假如你的新硬盘为ad1, 而将来的根分区将是ad1s1a, 你可以先创建文档系统:
newfs /dev/ad1s1a
mount /dev/ad1s1a /mnt
cd /mnt
复制:
dump -f- / | restore -f- -r
这是把老的根文档系统复制输出到管道,restore从管道里读数据,写入当前目录所在的文档系统。
按部就班复制其他文档系统
复制完後,也许 要修改新硬盘下的/etc/fstab,安装上新的分区,摘下老硬盘就可以了。
再补充一点,如果你要复制的是另外一台机器,可以用rsh,这样就可以通过网络把一个分区数据传送到另外一台机器,不需要NFS,不需SAMBA等就可以解决问题,而cp就很难作到了。
newfs /dev/ad1s1a
mount /dev/ad1s1a /mnt
cd /mnt
rsh -l yourname thathost 'dump -f- /' | restore -f- -r
linux怎么给一个普通用户reboot权限?
====================================
分四种情况讨论:
1.让任何人(包括根本不拥有系统帐号的人)都可以通过控制台reboot
在/etc/inittab文件中保留ca::ctrlaltdel:/sbin/shutdown -t3 -r now
这一行。这样全国人民都可以reboot你的机器,只要你把控制台交出来。
2.让所有系统用户都可以reboot
执行# > /etc/security/console.apps/reboot即可。这就在console.apps目录下生成了一个空文件,文件名就是授权的 application。以上路径是针对Mandrake系统而言的,其他系统我不清楚。不过,真正高雅的Mandraker或许根本就不会去靠 “>”来生成这个文件——他们会使用msec来进行控制的。
3.让指定的用户才可以reboot
假设我们要让用户zhizunbao拥有reboot的权限,我们靠uid/gid来完成控制:
# groupadd reboot
# cd /usr/local
# mkdir reboot
# chown root:reboot reboot/
# chmod 750 reboot/
# cd reboot
# cp /sbin/reboot .
# chmod 4755 reboot
# usermod -G reboot zhizunbao
现在,zhizunbao就可以运行/usr/local/reboot/reboot来重启动机器。
4.在一台不设普通用户的机器上启用口令验证reboot
这实际上是靠添加一个关机帐号来实现的,该帐号的shell就是加了s位的/sbin/halt,并且口令只有少数维护人员知道。我们这里采用的就是第4套方案。
怎样知道自己的机器上有哪些服务在运行
====================================
若一台机器运行有很多不需要的服务,那么被攻击者入侵的可能性就会大大加大,因此作为管理员就应该经常查看系统运行有哪些服务。
首先查看系统运行的进程
若需要查看系统当前运行的所有进程,就需要用如下命令:
# ps auxw
其中参数a表示显示系统中所有用户的的进程;u表示输出进程用户所属信息;x表示也显示没有控制台的进程;若显示行太长而被截断则可以使用f参数;
查看系统监听的服务
# netstat -ln
l表示显示当前系统监听的端口信息;n表示端口按照端口号来显示,而不转换为service文件中定义的端口名;若希望了解各个端口都是由哪些进程监听则可以使用p参数。
  若发现不需要的服务,可以使用linuxconf或ntsysv命令来关闭这些服务在系统启动时自启动,然后重新启动系统则这些服务将在运行。
有些服务是由inetd超级服务器来监控的,则需要标记/etc/inetd.conf来关闭这些服务。

查询端口对应的服务
====================================
# lsof -i :端口号
查询此端口对应的服务。
vi 中设置自动缩进
====================================
:set autoindent
:set ai
取消
:set noautoindent
:set noai
如何使linux系统对ping不反应
====================================
  在linux里,如果要想使ping 没反应也就是用来忽略icmp包。可以用:
  echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
  若想恢复就用:
  echo 0 > /proc/sys/net/ipv4/icmp_echo_ignore_all
如何实现多网卡bondin
====================================
  使用多块网卡虚拟成为一块网卡,具有相同的IP地址。这项技术其实在sun和cisco中已经存在,分别称为Trunking和etherchannel技术,在Linux中,这种技术称为bonding。
  因为bonding在内核2.4.x中已经包含了,只需要在编译的时候把网络设备选项中的Bonding driver support选中就可以了。
  然后,重新编译核心,重新起动计算机,执行如下命令:
  ismod bonding
  ifconfig eth0 down
  ifconfig eth1 down
  ifconfig bond0 ipaddress
  ifenslave bond0 eth0
  ifenslave bond0 eth1
  现在两块网卡已经象一块一样工作了.这样可以提高集群节点间的数据传输.
  你最好把这几句写成一个脚本,再由/etc/rc.d/rc.local调用,以便一开机就生效.
  bonding对于服务器来是个比较好的选择,在没有千兆网卡时,用两三块100兆网卡作bonding,可大大提高服务器到交换机之间的带宽.但是需要在交换机上设置连接bonding网卡的两个口子映射为同一个虚拟接口。
字符终端关掉喇叭
将/etc/inputrc中的set bell-style none 前的#去掉,或echo "set bell-style none" >> ~/.bashrc

/etc/fstab 文件说明
=======================
使用编辑器来修改 /etc/fstab (eg. vi /etc/fstab)
功能:存放文件系统与目录结构对应资料的档案
fstab 栏位说明:
第一栏(fs_spec): 实际的设备名称
第二栏(fs_file): 对应到的目录结构,挂载点
第三栏(fs_vfstype):该分区的文件系统格式,常见的有:
ext2、ext3,xfs,reiserfs,vfat(fat32),ntfs iso9660、nfs、swap
第四栏(fs_mntops): 在 mount 时的参数
第五栏(fs_freq): 在使用 dump 时是否记录,不需要则输入0
第六栏(fs_passno): 决定在开机时执行 fsck 的先后顺序
例子 :
IDE 硬盘分成两个 partition 与一个 swap,还有一台光驱跟一台软驱的情形 :  
/dev/hda1   /            ext2    defaults        1 1
/dev/hda6   swap         swap    defaults        0 0
/dev/cdrom  /mnt/cdrom   iso9660 noauto,user,ro  0 0
/dev/fd0    /mnt/floppy  ext2    noauto,owner    0 0
none        /proc        proc    defaults        0 0
none        /dev/pts     devpts  gid=5,mode=620  0 0
/dev/hda2 /mnt/wind  vfat defaults,iocharset=cp936,umask=000 0 0
为现有系统做启动盘
====================================
# mkbootdisk --device /dev/fd0 `uname -r`
如何去掉nvidia显卡启动时的logo画面
=======================================
在/etc/X11/XF86Config文件
Driver "nvidia" 那节,加入
Option “NoLogo”
让wget 自动走代理下载
=======================================
在用户主目录下建立.wgetrc 文件,格式举例如下
http-proxy=205.155.212.11:80
ftp-proxy=205.155.212.11:80
修改mozilla,mozilla firefox系列浏览器隐藏参数
=======================================
在地址栏输入 about:config
[img]http://www.linuxsky.net/images/snapshot5.png[/img:d3fd803243]
adsl共享上网,由adsl做网关然后nat
[code:d3fd803243]echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o ppp0 -j MASQUERADE[/code:d3fd803243]
如何使新用户首次登陆后强制修改密码(猫小)
================================
#useradd -p '' testuser; chage -d 0 testuser

日志维护工具logrotate(hotbox)
================================
在/etc/logrotate.conf中配置,作用:定义log文件达到预定的大小或时间时,自动压缩log文件


让vi不响铃(sakulagi)


用tar 来备份恢复linux系统举例
===============================================
tar -cjpvf /mnt/wind/linux.tar.bz2 / --exclude=/mnt
tar -**vf /mnt/wind/linux.tar.bz2 -C /

adsl 拨号成功后无法上网
===============================================
/sbin/route add default ppp0


如何在RED Hat Linux 防一用户两个人用登陆
===================================
直接在shell里面,就是你的命令行下面输入
echo "* hard maxlogins 2" &gt;&gt; /etc/security/limits.conf
然后回车,这个命令会将内容
* hard maxlogins 2
写到文件/etc/security/limits.conf中

还要防终端呀,那就继续
echo "session required pam_limits.so" &gt;&gt; /etc/pam.d/login

KDM 允许root登录
==================================
修改 /etc/kde3/kdm/kdmrc
找到
AllowRootLogin=false
修改为
AllowRootLogin=true


从rpm文件中解出某个文件的三种方法
==============================================
Peck Dickens的两种方法

1) 使用Midnight Commander (mc), 翻看rpm内部文件,并将某文件拷贝出来

2) 命令行方式
cd /tmp
md rpmtmp
cd rpmtmp
rpm2cpio {FullNameOfRPM} | cpio -iumd
注意,完成后请删除临时文件

Pam Roberts的一种方法

3) 使用rpm-utils-1.5-1.noarch.rpm中的rpm-get命令,该rpm可以从rpmfind.net获取

在vi 中查找一个词组后,取消对查字符的长期高亮
===================================================
临时取消,命令“ :noh ”
永久取消,编辑 ~/.exrc ,加入新的配置命令 "set nohlsearch"
ps 有趣的办法:搜寻一个很长的手指随机词组, /asdfakjsdyhfajhf .


1:HOST的乐趣: Host能够用来查询域名,然而它可以得到更多的信息。host -t mx linux.com可以查询出Linux.com的MX记录,以及处理Mail的Host的名字。Host -l linux.com会返回所有注册在linux.com下的域名。host -a linux.com则会显示这个主机的所有域名信息。

2:找到命令需要的文件,也许你希望搞清楚一个命令它到底使用到了什么其他相关文件和命令?试试ldd就可以了。如ldd traceroute,返回结果会是libc.so.6,lid-linux.so.2

3:定时!定时!你希望定时处理吗?你肯定希望能够让你轻松一些!crontab可以听候您的指示。命令行中输入:crontab -e这进入文本编辑器,再在其中输入0 5 * * * wget -t3 -I/home/tom/URLs -N。这样每天早上5点就会运行这个wget命令。前五个是时间参数:分 时 日 月 星期几。你可以 0 5 * * 6,0 command line here,这样就在每个星期六和星期天5am运行。你需要man 5 crontab来得到更为详细的解释。随便说一下,wget是一个取web页面的小程序。 console顶部显示现在时间:在/etc/bashrc或~/.bashrc中加入环境变量PROMPT_COMMAND。export PROMPT_COMMAND='echo -ne "33733[2;999r33[1;1H33[00;44m33[K"`date`"33[00m338"'  

4:使用多达23个的虚拟终端(VT),假设你现在有6个,仿照/etc/inittab种的文件,加入
8:234:respawn:/sbin/getty 38400 tty8
24:234:respawn:/sbin/getty 38400 tty24
要跳过VT7,因为X使用这个终端。然后telinit q以重新加载配置文件。使用左Alt+Fn在1-6和8-12件切换,使用右Alt+Fn键在13-24之间切换。

5:Console切换:左Alt-方向键,循环切换console。效果和Alt+Fn一样。

6:简单的进行Dos转Unix:如果你的文本文件中每行结尾都有讨厌的Ctrl-M的话,使用这句命令能够快速的解决问题 sed 's/^M//' filename > newfilename

7:快速建立MP3播放列表:find /mnt/cdrom -name *.mp3 -print >> cd1.play.list.这样就会把CDRom中的所有MP3文件建立一个名为cd1.play.list的文件。然后使用xmms 或 x11amp就能进行播放了。

8:/proc中的重要信息
cat /proc/cpuinfo - CPU (i.e. vendor, Mhz, flags like mmx)
cat /proc/interrupts - 中断
cat /proc/ioports - 设备IO端口
cat /proc/meminfo - 内存信息(i.e. mem used, free, swap size)
cat /proc/partitions - 所有设备的所有分区
cat /proc/pci - PCI设备的信息
cat /proc/swaps - 所有Swap分区的信息
cat /proc/version - Linux的版本号

9:真正的Console界面:说实话无论是KDE和GNome我都没什么喜欢的,毕竟受Windows熏陶了这么长时间了。但是在 Console下其实才是真正的Cool界面。使用SVGATextMode可以让你的命令行界面达到132x60(标准是80x25)。我的486 (Trident9440)上使用的是132x43,那密密麻麻的字符真象Hollywood的几年前的电影(现在连电影里都是Windows了)给人感觉神秘兮兮的。配置文件在/etc/TextConfig中,不过如果你的XWindow都无法正常显示的话,那估计这也用不了了(毕竟是好几年前的东西了)。

10:让你的硬盘飞起来: Linux下也可以使用32Bit I/O和DMA。使用/sbin/hdparm -c1 /dev/hda(hdb,hdc..)打开32Bit传输模式,使用命令 /sbin/hdparm -d1 /dev/hda(hdb,hdc...) 打开DMA。最后使用/sbin/hdparm -k1 /dev/hda 以使硬盘在Reset之后保持上面的设定,这么一来,硬盘读写速度应该可以提高一倍以上。

11:微调你的Ext2: Linux下的命令tune2fs可以设定最大Mount次数(因为Linux即时在mounted clean状况下,达到一定的mount次数后,会强制e2fsck对于我这样的每天开关机的来说很不方便),tune2fs命令的其他用途请参见 man。

12:禁止在Netscape中显示闪烁(Blink)Tag: ~/.Xdefaults中修改 Netscape*blinkingEnabled: False 之后运行 xrdb -Load ~/.Xdefaults

13:随时改变XTerm的字体大小:shift加+/-

14:Ext2的文件属性: lsattr列出属性,chattr可以改变属性。如 chattr +A filename 则系统不会修改其last access time属性;chattr +i 则可以避免被rm掉。

15:SIS6326显卡的配置:在Device中加入:DacSpeed 110 Option "no_bitblt" Option "sw_cursor"

16:使用strings可以把字符串从Binary文件中分离出来

17:你大概已经习惯了使用Top来观察系统使用情况,那就使一使使用Vmstat 1来看看cpu的空闲程度(显示结果的最后一项)。

18:使用Watch命令,反复执行特定命令,如"watch ls -l /tmp/blah",将一直观察文件的大小改变。

19:使用pppstats得到ppp连接的数据。

20:显示远程机器的NFS: showmount -e hostname

21:使用autofs免去你每次mount和umount之苦,再RedHat6.0中带了autofs的rpm,只要安装好之后便可以享受自动加载文件系统的方便。在/misc目录下,创建一个cd目录,如果使用其自带的auto.misc和auto.master的话,马上就可以实现自动加载功能了。

22:在Bash Shell中,可以用!重复上几次输入过的命令,这样就可以省去往上滚动的麻烦了。使用ctl+R可以互动的检索以前使用过的命令行。

23:使用cd -在上两个cd /directory/操作中切换。如先是cd /home/foo接着是 cd /home/maison则接着打cd - 会进入 /home/foo再打cd - 会进入 /home/maison。

24:Bash中的快捷键:ESCAPE-B 会向前条一个单词,Ctrl-K会删除从光标到行末所有字符。

25:在PS命令中使用grep,例:ps auxw | grep gpm 会列出所有含有gpm字符的进程。

26:在同一个命令行上同时执行多条命令:用分号分隔。如编译一个源文件:type ./configure ; make ; make install 。之后你就可以去散步了(不要在电脑前花费了太多时间了,生活中还有很多美好的东西。)

27:把运行结果输出到一个文件中,如 ls -l > foo.file。

28:使用Top命令监视系统状况,内存使用情况,开机时间,进程状况,每个用户的使用情况等等。使用S命令可以改变其刷新的时间:如.5将会使其每半秒钟更新一次。

29:同时Unzip多个文件,unzip *不正确,而是unzip "*"。

30:建立符号连结,这大概是最有用的功能了,ln -s real-file symbolic-file,无论是设备,文档还是脚本程序。

31:改变显示的单位,如du -hm 将以M为单位输出结果,同样的du -hk则以K为单位输出。

32:自动Logoff,在~/.bashrc中加入这么一句话:TMOUT=300则会在5分钟之后logoff,不过如果less这类程序运行则不会启动。

33:在使用less 或 more时,直接启动编辑器:在less中打入v键,会立即启动vi或其他你在环境变量中指明的编辑器,但是more只能使用vi

作者: compare2000    时间: 2014-05-24 00:13
windows&linux环境设置相关  一、CC使用技巧

1、显示某一天的更新记录:

cleartool lsthis -r -since 日期(如30-Jul)

2、查找本地所有private文件

cleartool ls -r -view_only



二、网络相关

1、nslookup ip

2、nbtstat -a  ip/主机名 通过ip/主机名,看主机名/IP

3、net share 查看共享文件夹



三、putty登陆linux服务器中文乱码

设置成功后,需要重新登陆putty

vi ~/.bashrc

文件末尾添加:
let &termencoding=&encoding
set fileencodings=utf-8,gbk,utf-16,big5

设置成功后,需要重新登陆putty



四、设置vim显示行号、语法高亮、自动缩进

set nu

set autoindent

set cindent

五、VIM Tutorial



六、linux常用命令

1、统计文件夹

统计总数大小

du -sh /var/html/wwwdu -sm * | sort -n //统计当前目录大小 并安大小 排序du -sk * | sort -ndu -sk * | grep guojf //看一个人的大小du -m | cut -d "/" -f 2 //看第二个/ 字符前的文字
查看此文件夹有多少文件 /*/*/* 有多少文件

du xmldb/du xmldb/*/*/* |wc -l2、添加linux用户

    1)  sudo adduser 用户名

    2)  sudo usermod -G 用户名群组

    3)  sudo smbpasswd -a w54542 #添加用户

    4)  sudo smbpasswd -e w54542 #激活用户
Linux进程管理命令详解  图4-8第二行代码中,USER表示启动进程用户。PID表示进程标志号。%CPU表示运行该进程占用CPU的时间与该进程总的运行时间的比例。%MEM表示该进程占用内存和总内存的比例。VSZ表示占用的虚拟内存大小,以KB为单位。RSS为进程占用的物理内存值,以KB为单位。TTY表示该进程建立时所对应的终端,"?"表示该进程不占用终端。STAT表示进程的运行状态,包括以下几种代码:D,不可中断的睡眠;R,就绪(在可运行队列中);S,睡眠;T,被跟踪或停止;Z,终止(僵死)的进程,Z不存在,但暂时无法消除;W,没有足够的内存分页可分配;<高优先序的进程;N,低优先序的进程;L,有内存分页分配并锁在内存体内(实时系统或I/O)。START为进程开始时间。TIME为执行的时间。COMMAND是对应的命令名。

  
(点击查看大图)图4-8  ps-aux命令详解

应用实例如下。

在进行系统维护时,如果CPU负载突然增加,而又不知道是哪一个进程造成的情况。使用命令:

#ps aux | sort +3n

说明  因为%CPU选项在第三列,所以sort选3作为参数,+表示升序输出结果。

8.pstree命令

pstree命令列出当前的进程,以及它们的树状结构。

格式:pstree [选项] [pid|user]

主要选项如下。

-a:显示执行程序的命令与完整参数。

-c:取消同名程序,合并显示。

-h:对输出结果进行处理,高亮显示正在执行的程序。

-l:长格式显示。

-n:以PID大小排序。

-p:显示PID。

-u:显示UID信息。

-G:使用VT100终端编码显示。

-U:使用UTF-8(Unicode)编码显示。

说明:使用ps命令得到的数据精确,但数据庞大,这一点对掌握系统整体概况来说是不容易的。pstree正好可以弥补这个缺憾。它能将当前的执行程序以树状结构显示。pstree支持指定特定程序(PID)或使用者(USER)作为显示的起始。

应用实例如下。

进程启动的时候可能会产生自己的一个子进程。运行pstree命令就可以很容易地看到这些信息。以超级用户权限运行pstree:

#init-+-apmd
|-atd
|-bdflush
|-gconfd-2
|-gdm-binary---gdm-binary-+-X
|                         `-startkde-+-kwrapper
|                                    `-ssh-agent
|-gpm
|-httpd---8*[httpd]
……下略

命令对程序名称相同的会自动合并,所有"|-httpd---8*[httpd]"即表示系统中有8个httpd进程产生的子进程。

9.top命令显示进程

top命令用来显示系统当前的进程状况。

格式:top [选项]

主要选项如下。

d:指定更新的间隔,以秒计算。

q:没有任何延迟的更新。如果使用者有超级用户,则top命令将会以最高的优先序执行。

c:显示进程完整的路径与名称。

S:累积模式,会将已完成或消失的子进程的CPU时间累积起来。

s:安全模式。

i:不显示任何闲置(Idle)或无用(Zombie)的进程。

n:显示更新的次数,完成后将会退出top。

说明:top命令和ps命令的基本作用是相同的,都显示系统当前的进程状况。但是top是一个动态显示过程,即可以通过用户按键来不断刷新当前状态。这里结合图4-9来说明它给出的信息。


  
(点击查看大图)图4-9  top命令详解


在图4-9中,第一行表示的项目依次为当前时间、系统启动时间、当前系统登录用户数目、平均负载。第二行显示的是所有启动的、目前运行的、挂起(Sleeping)的和无用(Zombie)的进程。第三行显示的是目前CPU的使用情况,包括系统占用的比例、用户使用比例、闲置(Idle)比例。第四行显示物理内存的使用情况,包括总的可以使用的内存、已用内存、空闲内存、缓冲区占用的内存。第五行显示交换分区的使用情况,包括总的交换分区、使用的、空闲的和用于高速缓存的交换分区。第六行显示的项目最多,下面列出了详细解释。

PID(Process ID):进程标志号,是非零正整数。USER:进程所有者的用户名。PR:进程的优先级别。NI:进程的优先级别数值。VIRT:进程占用的虚拟内存值。RES:进程占用的物理内存值。SHR:进程使用的共享内存值。STAT:进程的状态,其中S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值是负数。%CPU:该进程占用的CPU使用率。%MEM:该进程占用的物理内存和总内存的百分比。TIME:该进程启动后占用的总的CPU时间。COMMAND:进程启动的启动命令名称,如果这一行显示不下,进程会有一个完整的命令行。top命令使用过程中,还可以使用一些交互的命令来完成其他参数的功能。这些命令是通过快捷键启动的。

<空格>:立刻刷新。

P:根据CPU使用大小进行排序。

T:根据时间、累计时间排序。

q:退出top命令。

m:切换显示内存信息。

t:切换显示进程和CPU状态信息。

c:切换显示命令名称和完整命令行。

M:根据使用内存大小进行排序。

W:将当前设置写入~/.toprc文件中。这是写top配置文件的推荐方法。

可以看到,top命令是一个功能十分强大的监控系统的工具,对于系统管理员而言尤其重要。但是,它的缺点是会消耗很多系统资源。

http://book.51cto.com/art/200902/111563.htm

作者: compare2000    时间: 2014-05-24 00:15
改变Linux字体和背景颜色  使用Linux的朋友一定熟悉文本界面上的黑底白字和X上的白底黑字,这两种颜色可以说是经典的搭配,清晰明了。但是你也可以根据自己的喜好使字体和背景换个颜色。
下面我介绍一下RedHat的字体和背景颜色的改变方法:
命令:
PS1="[\e[32;1m\u@\h \W]\\$" 看看是什么效果

export PS1="[\e[32;1m\u@\h \W]\\$" 两者的区别请查看环境变量的相关资料
解释:
PS1:一个环境变量,就称它为提示符变量吧,注意大小写,且后面是数字“1”
不是字母“I”
\e[32;1m:这就是控制字体和背景颜色的转义字符,30~37是字体颜色、40~47是背景颜色

子中的32;1m数字的位置是可以对调的如\e[1;32m,如果是在X环境下可以更换一下1的范围0~10,可能有的没用处:0或者不写(\e[0;
32m或\e[;32m)显示浅颜色,1:显示高亮
4:加下划线.....如果改后的效果不好,但是又还原不了,那就不写m前面的数字,如\e[32;m,或者直接注销再登陆
\u \h \W:这是一些转义字符,下面详细解释:
\d :代表日期,格式为weekday month date,例如:"Mon Aug 1"
\H :完整的主机名称。例如:我的机器名称为:fc4.linux,则这个名称就是fc4.linux
\h :仅取主机的第一个名字,如上例,则为fc4,.linux则被省略
\t :显示时间为24小时格式,如:HH:MM:SS
\T :显示时间为12小时格式
\A :显示时间为24小时格式:HH:MM
\u :当前用户的账号名称
\v :BASH的版本信息
\w :完整的工作目录名称。家目录会以 ~代替
\W :利用basename取得工作目录名称,所以只会列出最后一个目录
\# :下达的第几个命令
\$ :提示字符,如果是root时,提示符为:# ,普通用户则为:$
\n :新建一行
字体并不局限于一个颜色,可以有多个颜色:
PS1="[\e[32;1m\u@\e[35;1m\h \e[31;1m\W]\\$"
以上两个命令在注销后再登陆就失效了,用下面方法使其永久生效:
vi /etc/profile
在“export PATH .....”下面添加一行:export PS1="[\e[32;1m\u@\h \W]\\$"
注销再登陆,就成功了,如果没生效,使用source /etc/profile 命令试试,或者直接重启机器。
-e     enable interpretation of the backslash-escaped characters listed below
字背景颜色范围:40----49
40:黑
41:深红
42:绿
43:黄色
44:蓝色
45:紫色
46:深绿
47:白色
字颜色:30-----------39
30:黑
31:红
32:绿
33:黄
34:蓝色
35:紫色
36:深绿
37:白色
===============================================ANSI控制码的说明
\33[0m 关闭所有属性
\33[1m 设置高亮度
\33[4m 下划线
\33[5m 闪烁
\33[7m 反显
\33[8m 消隐
\33[30m -- \33[37m 设置前景色
\33[40m -- \33[47m 设置背景色
\33[nA 光标上移n行
\33[nB 光标下移n行
\33[nC 光标右移n行
\33[nD 光标左移n行
\33[y;xH设置光标位置
\33[2J 清屏
\33[K 清除从光标到行尾的内容
\33[s 保存光标位置
\33[u 恢复光标位置
\33[?25l 隐藏光标
\33[?25h 显示光标
例子
echo -e "\033[41;36m hello world \033[0m"
其中41的位置代表底色, 36的位置是代表字的颜色




在linux用户层程序,串口打印输出不同颜色字体的方法:

#define PRINT_RED(s)    printf("\033[0;31m%s\033[0;39m",s);
#define PRINT_GREEN(s)    printf("\033[0;32m%s\033[0;39m",s);
#define PRINT_YELLOW(s)    printf("\033[0;33m%s\033[0;39m",s);
#define PRINT_BULE(s)    printf("\033[0;34m%s\033[0;39m",s);
#define PRINT_PURPLE(s)    printf("\033[0;35m%s\033[0;39m",s);/*紫色*/

先举个例子:
以红色在终端上打印出“Hello, world”,对应的代码如下:
printf("\033[31m\033[1m"; // 开始以红色打印
printf("Hello, world\n";

要恢复默认颜色打印,只需要再打印如下一句:
printf("\033[0m";


从上面的例子可以看出printf("\033[31m\033[1m"; 和printf("\033[0m";是成对的。
Linux环境下的C/C++基础调试技术1  Author:gnuhpc
WebSite:blog.csdn.net/gnuhpc

1.调试技术的几个准则

•惊喜准则:找到错误是一种惊喜,心理上不要畏惧而是要怀着感恩的心去面对。
•从小处开始准则:刚开始测试的使用从小处着手,暂时不涉及边界数据,虽然这样可能会掩盖一些Bug,但是这样或许能查到最主要的Bug,例如你的程序包含了一个巨大的循环体,最容易发现的Bug在第一个循环或第二次循环执行的时候。
•自顶向下准则:优先选择step over而不是step into,以节省时间。
•Segmentation Fault准则:出现段错误时,第一个想到的不应该是printf而是Debugger,因为在调试器中你能看到你的哪一行代码导致了错误,更重要的是你可以通过backtrace等工具得到更多有用的信息。
•折半查找准则:在寻找bug时可以充分利用编辑器等工具来进行折半查找,具体在后边有例子说明。
2.Linux下代码调试工具

主要使用的GDB,以及基于GDB的图形化工具,如DDD或eclipse,选择上看个人习惯了。

命令行式的GDB启动较快,可以在ssh终端下使用,操作简洁,并且在调试GUI程序时不会崩溃,但较之图形化则在单步调试或设置断点时非常不方便。

当然你可以使用Vim等编辑器的插件或者补丁(clewn or vimGDB)来弥补这一缺憾,并且在GDB6.1以上的版本你可以使用GDB -tui这个模式(或者在GDB的命令行模式下按CTRL-x-a)打开一个类似于图形界面的文本界面模式,在这个界面中你可以使用上下键查看源代码(CTRL-P 和 CTRL-N完成输入过的命令的查看).

或者你还可以使用cGDB这个工具(很庆幸这个项目在停止了三年后又有人开始维护了),这个工具是将GDB用curses包装了一下,提供了一些很好用的feature(Esc和i键在代码和命令框间切换;在代码框中支持vim型的操作;在命令框中支持tab键补全命令;在移动到想加入断点的行(行号为高亮白色)直接用空格键,设定好后行号会变红;)。另外,在调试C-S程序时推荐使用eclipse。

在本文中,重点介绍ddd的操作,因为这个工具即结合了GDB命令行和图形界面的操作。其余请参阅各个工具的手册。

3.GDB命令行最基本操作

•设置断点:b LineNumber
•运行程序:r args1 args2 ...
•彻底终止程序:kill
•单步执行:n(TIPs1:可以按回车重复上一次操作,在单步调试时这个feature很有用)。
•单步进入:s
•继续执行:c
•设置临时断点:tb LineNumber 可以理解为一次性断点,与断点不同,临时断点只在第一次执行时起作用。
•查看变量:p
•设置观察点:
•w Expression,当Expression是一个变量名时,这个变量变化时会停止执行;你也可以使用条件来限定,比如w (z>2,当z大于28时,程序停止。注意观察点一般使用在更大范围上的变量,而不是本地变量,因为在局部变量上设置的观察点在局部结束时(比如该变量所在的函数执行结束时)就被取消了。
•当然这并不包含main的情况,因为main函数执行结束后程序就结束了。
•查看栈帧:
•栈帧指的是在一个函数调用时,该函数调用的运行信息(包含本地变量、参数以及函数被调用的位置)存储的地方。每当一个函数被调用时,一个新的帧就被系统压入一个由系统维护的帧,在这个栈的顶端是现在正在运行的函数信息,当该函数调用结束时被弹出并析构。
•在GDB中,frame 0为当前帧,frame 1为当前帧的父帧,frame 2为父帧的父帧,等等,用down命令则是反向的。这是一个很有用的信息,因为在早期的一些帧中的信息可能会给你一些提示。
•backtrace查看整个帧栈
•注意:在帧中来回并不影响程序的执行。
实例:插入排序算法调试

用伪代码描述这个过程如下:



拟调试代码如下:

//
//
// be sorted
// workspace array  
// current number of elements in y
int i;
   for (i = 0; i < num_inputs; i++)
}
int k;
void insert(int new_y)
if (num_y = 0)  { // y empty so far, easy case
      return;
   // need to insert just before the first y
for (j = 0; j < num_y; j++)  {
// shift y[j], y[j+1],... rightward
return;
   }
void process_data()
   for (num_y = 0; num_y < num_inputs; num_y++)
// among y[0],...,y[num_y-1]
}
int i;
%d\n",y[i]);
int main(int argc, char ** argv)
   process_data();
}我们编译一下:

gcc -g -Wall -o insert_sort ins.c

注意我们要使用-g选项告诉编译器在可执行文件中保存符号表——我们程序中变量和代码对应的内存地址。

现在我们开始运行一下,我们使用“从小处开始准则”,首先使用两个数进行测试:

./insert_sort 12 5

我们发现该程序没有退出,貌似进入了一个死循环。我们开始使用ddd调试这个程序:

ddd insert_sort

运行程序,传入两个参数:

r 12 5

此时程序一直运行不退出,按Ctrl+C暂停程序的执行

(GDB) r 12 5
^C
Program received signal SIGINT, Interrupt.
0x080484ff in insert (new_y=3) at insert_sort.c:45
/home/gnuhpc/MyCode/Debug/Chapter_01/insert_sort/pg_019/insert_sort.c:45:939:beg:0x80484ff
(GDB)



我们可以看到程序停止在第49行。我们看一下num_y现在的值:

(GDB) p num_y
$1 = 1

这里的$1指的是你要GDB告诉你的第一个变量。找到了这个地方后,我们看看在num_y=1时都发生了什么,我们在insert函数(第27行)设置断点(你也可以直接使用break insert在这个函数的入口设置断点),并且设置GDB在断点1处(你可以通过info break命令查看断点)只当num_y==1时才停止:

(GDB) b 27
Breakpoint 1 at 0x80484a1: file insert_sort.c, line 27.
(GDB) condition 1 num_y==1
(GDB) 

上述命令也可以使用break if合一:

(GDB) break 27 if num_y==1

然后再运行程序,随后用n单步调试发现我们跳到了该函数的出口处:



此时我们看看num_y的值,以便查看到底这个for循环执行的情况。

(GDB) p num_y
$2 = 0

此时的情况是我们进入这个函数时num_y为1,但是现在num_y为0,在中间这个变量被改变了。现在你知道Bug就在30-36行间。同时,通过单步调试你发现31-33行被跳过了,34、35行为注释,那么Bug就在第30或第36行间了。

我们现在仔细看这两行就能得出结论了:30行有个典型的if判断条件写成赋值的错误,致命的是这个变量是全局变量,直接导致49行的for循环变量一直被重置。我们修改后重新编译(可以另开一个编辑器,不用退出ddd),然后再运行

(GDB) r 12 5
5
0

虽然没有了死循环,但是结果还是不对的。

请注意,初始的时候数组y是空的,在#49进行第一次循环时,y[0]应该为12,在第二个循环中,程序应该挪动12为5腾出位置插入,但是此时这个结果看上去是5取代了12。

此时单步调试进入for循环,#37看y[0]的值,的确是12。我们执行到scoot_over函数时,根据自顶向下准则我们单步跳过,继续执行到#41,看看结果对错再决定是不是要单步进入scoot_over函数:



我们发现12根本就没有被移动,说明scoot_over函数有问题,我们去掉insert函数入口的断点,在scoot_over入口处设置断点,当num_y=1的时候终止:b scoot_over if num_y==1。进一步单步调试后发现这个#23的for循环就没有执行。

(GDB) p jj
$12 = 0
(GDB) p k
$13 = 0

我们看到是因为没有满足for循环条件而不能进入循环。在这里12应该从y[0]移动到y[1],那么我们确定是循环的初始化错误,应该为k = num_y,将这个地方修改后编译运行,程序出现段错误。我们清空所有的断点,然后在

(GDB) r

Program received signal SIGSEGV, Segmentation fault.
0x08048483 in scoot_over (jj=0) at insert_sort.c:24
(GDB)

这里指出在24行出现seg fault,那么要么k超过了数组界限,要么k-1为负的。打印一下k的值,我们就发现:

(GDB) p k
$14 = 992
(GDB)

远远超过k应该有的值。查看num_y 为1,说明在处理第二个要排序的数时出错,再打印jj值,发现为0,就发现我们的for循环k++应该改为k—。

编译运行,发现ok。但是运行多个数据就又出错了:

(GDB) r 12 5 19 22 6 1
1
5
6
12
0
0

Program exited with code 06.
(GDB)

我们看到结果中从19开始的排序都有问题,我们在for (j = 0; j < num_y; j++)  这一行行设置断点,条件为new_y==19的时候:

(GDB) break 36 if new_y==19
Breakpoint 10 at 0x80484b1: file insert_sort.c, line 36.
(GDB)

单步调试就发现我们没有对当要插入的元素大于所有元素时进行处理。在#44后加入y[num_y] = new_y;重新编译,运行程序正确,至此,我们通过一个简单的例子演示了一下如何使用GDB进行调试。

参考文献:
《Art of Debugging》
《Linux&reg; Debugging and Performance Tuning: Tips and Techniques》

Author:gnuhpc
WebSite:blog.csdn.net/gnuhpc

作者: compare2000    时间: 2014-05-24 00:16
Linux中top对应Solaris的prstat,Windows的tasklist或任务管理器。

top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。下面详细介绍它的使用方法。top是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命令,它将独占前台,直到用户终止该程序为止.比较准确的说,top命令提供了实时的对系统处理器的状态监视.它将显示系统中CPU最“敏感”的任务列表.该命令可以按CPU使用.内存使用和执行时间对任务进行排序;而且该命令的很多特性都可以通过交互式命令或者在个人定制文件中进行设定.

1.命令格式:

top [参数]

2.命令功能:

显示当前系统正在执行的进程的相关信息,包括进程ID、内存占用率、CPU占用率等

3.命令参数:

-b 批处理

-c 显示完整的治命令

-I 忽略失效过程

-s 保密模式

-S 累积模式

-i<时间> 设置间隔时间

-u<用户名> 指定用户名

-p<进程号> 指定进程

-n<次数> 循环显示的次数


4.使用实例:

实例1:显示进程信息

命令:

top

输出:

[root@TG1704 log]# top

top - 14:06:23 up 70 days, 16:44,  2 users,  load average: 1.25, 1.32, 1.35

Tasks: 206 total,   1 running, 205 sleeping,   0 stopped,   0 zombie

Cpu(s):  5.9%us,  3.4%sy,  0.0%ni, 90.4%id,  0.0%wa,  0.0%hi,  0.2%si,  0.0%st

Mem:  32949016k total, 14411180k used, 18537836k free,   169884k buffers

Swap: 32764556k total,        0k used, 32764556k free,  3612636k cached


  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                               

28894 root      22   0 1501m 405m  10m S 52.2  1.3   2534:16 java                                                                  

18249 root      18   0 3201m 1.9g  11m S 35.9  6.0 569:39.41 java                                                                  

2808 root      25   0 3333m 1.0g  11m S 24.3  3.1 526:51.85 java                                                                  

25668 root      23   0 3180m 704m  11m S 14.0  2.2 360:44.53 java                                                                  

  574 root      25   0 3168m 611m  10m S 12.6  1.9 556:59.63 java                                                                  

1599 root      20   0 3237m 1.9g  11m S 12.3  6.2 262:01.14 java                                                                  

1008 root      21   0 3147m 842m  10m S  0.3  2.6   4:31.08 java                                                                  

13823 root      23   0 3031m 2.1g  10m S  0.3  6.8 176:57.34 java                                                                  

28218 root      15   0 12760 1168  808 R  0.3  0.0   0:01.43 top                                                                    

29062 root      20   0 1241m 227m  10m S  0.3  0.7   2:07.32 java                                                                  

    1 root      15   0 10368  684  572 S  0.0  0.0   1:30.85 init                                                                  

    2 root      RT  -5     0    0    0 S  0.0  0.0   0:01.01 migration/0                                                            

    3 root      34  19     0    0    0 S  0.0  0.0   0:00.00 ksoftirqd/0                                                            

    4 root      RT  -5     0    0    0 S  0.0  0.0   0:00.00 watchdog/0                                                            

    5 root      RT  -5     0    0    0 S  0.0  0.0   0:00.80 migration/1                                                            

    6 root      34  19     0    0    0 S  0.0  0.0   0:00.00 ksoftirqd/1                                                            

    7 root      RT  -5     0    0    0 S  0.0  0.0   0:00.00 watchdog/1                                                            

    8 root      RT  -5     0    0    0 S  0.0  0.0   0:20.59 migration/2                                                            

    9 root      34  19     0    0    0 S  0.0  0.0   0:00.09 ksoftirqd/2                                                            

   10 root      RT  -5     0    0    0 S  0.0  0.0   0:00.00 watchdog/2                                                            

   11 root      RT  -5     0    0    0 S  0.0  0.0   0:23.66 migration/3                                                            

   12 root      34  19     0    0    0 S  0.0  0.0   0:00.03 ksoftirqd/3                                                            

   13 root      RT  -5     0    0    0 S  0.0  0.0   0:00.00 watchdog/3                                                            

   14 root      RT  -5     0    0    0 S  0.0  0.0   0:20.29 migration/4                                                            

   15 root      34  19     0    0    0 S  0.0  0.0   0:00.07 ksoftirqd/4                                                            

   16 root      RT  -5     0    0    0 S  0.0  0.0   0:00.00 watchdog/4                                                            

   17 root      RT  -5     0    0    0 S  0.0  0.0   0:23.07 migration/5                                                            

   18 root      34  19     0    0    0 S  0.0  0.0   0:00.07 ksoftirqd/5                                                            

   19 root      RT  -5     0    0    0 S  0.0  0.0   0:00.00 watchdog/5                                                            

   20 root      RT  -5     0    0    0 S  0.0  0.0   0:17.16 migration/6                                                            

   21 root      34  19     0    0    0 S  0.0  0.0   0:00.05 ksoftirqd/6                                                            

   22 root      RT  -5     0    0    0 S  0.0  0.0   0:00.00 watchdog/6                                                            

   23 root      RT  -5     0    0    0 S  0.0  0.0   0:58.28 migration/7


说明:

统计信息区:

前五行是当前系统情况整体的统计信息区。下面我们看每一行信息的具体意义。

第一行,任务队列信息,同 uptime 命令的执行结果,具体参数说明情况如下:

14:06:23 — 当前系统时间

up 70 days, 16:44 — 系统已经运行了70天16小时44分钟(在这期间系统没有重启过的吆!)

2 users — 当前有2个用户登录系统

load average: 1.15, 1.42, 1.44 — load average后面的三个数分别是1分钟、5分钟、15分钟的负载情况。

load average数据是每隔5秒钟检查一次活跃的进程数,然后按特定算法计算出的数值。如果这个数除以逻辑CPU的数量,结果高于5的时候就表明系统在超负荷运转了。

第二行,Tasks — 任务(进程),具体信息说明如下:

系统现在共有206个进程,其中处于运行中的有1个,205个在休眠(sleep),stoped状态的有0个,zombie状态(僵尸)的有0个。

第三行,cpu状态信息,具体属性说明如下:

5.9%us — 用户空间占用CPU的百分比。

3.4% sy — 内核空间占用CPU的百分比。

0.0% ni — 改变过优先级的进程占用CPU的百分比

90.4% id — 空闲CPU百分比

0.0% wa — IO等待占用CPU的百分比

0.0% hi — 硬中断(Hardware IRQ)占用CPU的百分比

0.2% si — 软中断(Software Interrupts)占用CPU的百分比

备注:在这里CPU的使用比率和windows概念不同,需要理解linux系统用户空间和内核空间的相关知识!

第四行,内存状态,具体信息如下:

32949016k total — 物理内存总量(32GB)

14411180k used — 使用中的内存总量(14GB)

18537836k free — 空闲内存总量(18GB)

169884k buffers — 缓存的内存量 (169M)

第五行,swap交换分区信息,具体信息说明如下:

32764556k total — 交换区总量(32GB)

0k used — 使用的交换区总量(0K)

32764556k free — 空闲交换区总量(32GB)

3612636k cached — 缓冲的交换区总量(3.6GB)


备注:

第四行中使用中的内存总量(used)指的是现在系统内核控制的内存数,空闲内存总量(free)是内核还未纳入其管控范围的数量。纳入内核管理的内存不见得都在使用中,还包括过去使用过的现在可以被重复利用的内存,内核并不把这些可被重新使用的内存交还到free中去,因此在linux上free内存会越来越少,但不用为此担心。

如果出于习惯去计算可用内存数,这里有个近似的计算公式:第四行的free + 第四行的buffers + 第五行的cached,按这个公式此台服务器的可用内存:18537836k +169884k +3612636k = 22GB左右。

对于内存监控,在top里我们要时刻监控第五行swap交换分区的used,如果这个数值在不断的变化,说明内核在不断进行内存和swap的数据交换,这是真正的内存不够用了。

第六行,空行。

第七行以下:各进程(任务)的状态监控,项目列信息说明如下:

PID — 进程id

USER — 进程所有者

PR — 进程优先级

NI — nice值。负值表示高优先级,正值表示低优先级

VIRT — 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES

RES — 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA

SHR — 共享内存大小,单位kb

S — 进程状态。D=不可中断的睡眠状态 R=运行 S=睡眠 T=跟踪/停止 Z=僵尸进程

%CPU — 上次更新到现在的CPU时间占用百分比

%MEM — 进程使用的物理内存百分比

TIME+ — 进程使用的CPU时间总计,单位1/100秒

COMMAND — 进程名称(命令名/命令行)


其他使用技巧:

1.多U多核CPU监控

在top基本视图中,按键盘数字“1”,可监控每个逻辑CPU的状况:



观察上图,服务器有16个逻辑CPU,实际上是4个物理CPU。再按数字键1,就会返回到top基本视图界面。

2.高亮显示当前运行进程

敲击键盘“b”(打开/关闭加亮效果),top的视图变化如下:

     

我们发现进程id为2570的“top”进程被加亮了,top进程就是视图第二行显示的唯一的运行态(runing)的那个进程,可以通过敲击“y”键关闭或打开运行态进程的加亮效果。

3.进程字段排序

默认进入top时,各进程是按照CPU的占用量来排序的,在下图中进程ID为28894的java进程排在第一(cpu占用142%),进程ID为574的java进程排在第二(cpu占用16%)。

      

    敲击键盘“x”(打开/关闭排序列的加亮效果),top的视图变化如下:

        

可以看到,top默认的排序列是“%CPU”。


4. 通过”shift + >”或”shift + <”可以向右或左改变排序列

下图是按一次”shift + >”的效果图,视图现在已经按照%MEM来排序。

         


实例2:显示 完整命令

命令:

top -c

输出:

      

说明:


实例3:以批处理模式显示程序信息

命令:

top -b

输出:

说明:


实例4:以累积模式显示程序信息

命令:

top -S

输出:

说明:


实例5:设置信息更新次数

命令:

  top -n 2

输出:

说明:

表示更新两次后终止更新显示


实例6:设置信息更新时间

命令:

top -d 3

输出:

说明:

表示更新周期为3秒


实例7:显示指定的进程信息

命令:

top -p 574

输出:




说明:

     5.top交互命令


在top 命令执行过程中可以使用的一些交互命令。这些命令都是单字母的,如果在命令行中使用了s 选项, 其中一些命令可能会被屏蔽。

h 显示帮助画面,给出一些简短的命令总结说明

k 终止一个进程。

i 忽略闲置和僵死进程。这是一个开关式命令。

q 退出程序

r 重新安排一个进程的优先级别

S 切换到累计模式

s 改变两次刷新之间的延迟时间(单位为s),如果有小数,就换算成m s。输入0值则系统将不断刷新,默认值是5 s

f或者F 从当前显示中添加或者删除项目

o或者O 改变显示项目的顺序

l 切换显示平均负载和启动时间信息

m 切换显示内存信息

t 切换显示进程和CPU状态信息

c 切换显示命令名称和完整命令行

M 根据驻留内存大小进行排序

P 根据CPU使用百分比大小进行排序

T 根据时间/累计时间进行排序

    W 将当前设置写入~/.toprc文件中
原文:
http://www.cnblogs.com/peida/archive/2012/12/24/2831353.html
作者: compare2000    时间: 2014-05-24 00:17
修改linux提示符的显示格式  Linux系统的 bash shell 环境下通常是由 PS1 这个 bash内置变量决定主命令提示符的显示格式,我们只需要将这个变量设置成我们需要的格式就能获得我们想要的视觉效果。

比如我现在用的:PS1="\[\033[32m\]\w <\u@\h> \t\[\033[m\]  ,效果如下:





可以执行export PS1="\[\033[32m\]\w <\u@\h> \t\[\033[m\] \\$ "在当前终端生效(重新登录失效),也可以在/etc/profile中添加这行永远生效。


Linux Shell常用技巧(十二) Shell编程  二十三. Bash Shell编程:

    1.  读取用户变量:
    read命令是用于从终端或者文件中读取输入的内建命令,read命令读取整行输入,每行末尾的换行符不被读入。在read命令后面,如果没有指定变量名,读取的数据将被自动赋值给特定的变量REPLY。下面的列表给出了read命令的常用方式:

命令格式 描述
read answer 从标准输入读取输入并赋值给变量answer。
read first last 从标准输入读取输入到第一个空格或者回车,将输入的第一个单词放到变量first中,并将该行其他的输入放在变量last中。
read 从标准输入读取一行并赋值给特定变量REPLY。
read -a arrayname 把单词清单读入arrayname的数组里。
read -p prompt 打印提示,等待输入,并将输入存储在REPLY中。
read -r line 允许输入包含反斜杠。


    见下面的示例(绿色高亮部分的文本为控制台手工输入信息):
    /> read answer        #等待读取输入,直到回车后表示输入完毕,并将输入赋值给变量answer
    Hello                       #控制台输入Hello
    /> echo $answer      #打印变量
    Hello

    #等待一组输入,每个单词之间使用空格隔开,直到回车结束,并分别将单词依次赋值给这三个读入变量。
    /> read one two three
    1 2 3                      #在控制台输入1 2 3,它们之间用空格隔开。
    /> echo "one = $one, two = $two, three = $three"
    one = 1, two = 2, three = 3

    /> read                  #等待控制台输入,并将结果赋值给特定内置变量REPLY。
    This is REPLY          #在控制台输入该行。
    /> echo $REPLY      #打印输出特定内置变量REPLY,以确认是否被正确赋值。
    This is REPLY

    /> read -p "Enter your name: "    #输出"Enter your name: "文本提示,同时等待输入,并将结果赋值给REPLY。
    Enter you name: stephen            #在提示文本之后输入stephen
    /> echo $REPLY
    stephen

    #等待控制台输入,并将输入信息视为数组,赋值给数组变量friends,输入信息用空格隔开数组的每个元素
    /> read -a friends
    Tim Tom Helen
    /> echo "I have ${#friends} friends"
    I have 3 friends
    /> echo "They are ${friends[0]}, ${friends[1]} and ${friends[2]}."
    They are Tim, Tom and Helen.

   2.  状态判断:
    test是Shell中提供的内置命令,主要用于状态的检验,如果结果为0,表示成功,否则表示失败。见如下示例:
    /> name=stephen
    /> test $name != stephen
    /> echo $?
    1
    需要注意的是test命令不支持Shell中提供的各种通配符,如:
    /> test $name = [Ss]tephen
    /> echo $?
    1
    test命令还可以中括号予以替换,其语义保持不变,如:
    /> [ $name = stephen ]
    /> echo $?
    0   
    在Shell中还提供了另外一种用于状态判断的方式:[[ expr ]],和test不同的是,该方式中的表达式支持通配符,如:
    /> name=stephen
    /> [[ $name == [Ss]tephen ]]
    /> echo $?
    0
    #在[[ expression ]]中,expression可以包含&&(逻辑与)和||(逻辑或)。
    /> [[ $name == [Ss]tephen && $friend == "Jose" ]]
    /> echo $?
    1
    /> shopt -s extglob   #打开Shell的扩展匹配模式。
    /> name=Tommy
    # "[Tt]o+(m)y"的含义为,以T或t开头,后面跟着一个o,再跟着一个或者多个m,最后以一个y结尾。
    /> [[ $name == [Tt]o+(m)y ]]
    /> echo $?
    0
    在Shell中还提供了let命令的判断方式: (( expr )),该方式的expr部分,和C语言提供的表达式规则一致,如:
    /> x=2
    /> y=3
    /> (( x > 2 ))
    /> echo $?
    1
    /> (( x < 2 ))
    /> echo $?
    0  
    /> (( x == 2 && y == 3 ))
    /> echo $?
    0
    /> (( x > 2 || y < 3 ))
    /> echo $?
    1

    下面的表格是test命令支持的操作符:

判断操作符 判断为真的条件
字符串判断   
[ stringA=stringB ] stringA等于stringB
[ stringA==stringB ] stringA等于stringB
[ stringA!=stringB ] stringA不等于stringB
[ string ] string不为空
[ -z string ] string长度为0
[ -n string ] string长度不为0
逻辑判断   
[ stringA -a stringB ] stringA和stringB都是真
[ stringA -o stringB ] stringA或stringB是真
[ !string ] string不为真
逻辑判断(复合判断)   
[[ pattern1 && pattern2 ]] pattern1和pattern2都是真
[[ pattern1 || pattern2 ] pattern1或pattern2是真
[[ !pattern ]] pattern不为真
整数判断   
[ intA -eq intB ] intA等于intB
[ intA -ne intB ] intA不等于intB
[ intA -gt intB ] intA大于intB
[ intA -ge intB ] intA大于等于intB
[ intA -lt intB ] intA小于intB
[ intA -le intB ] intA小于等于intB
文件判断中的二进制操作   
[ fileA -nt fileB ] fileA比fileB新
[ fileA -ot fileB ] fileA比fileB旧
[ fileA -ef fileB ] fileA和fileB有相同的设备或者inode值
文件检验   
[ -d $file ] or [[ -d $file ]] file为目录且存在时为真
[ -e $file ] or [[ -e $file ]] file为文件且存在时为真
[ -f $file ] or [[ -f $file ]] file为非目录普通文件存在时为真
[ -s $file ] or [[ -s $file ]] file文件存在, 且长度不为0时为真
[ -L $file ] or [[ -L $file ]] file为链接符且存在时为真
[ -r $file ] or [[ -r $file ]] file文件存在且可读时为真
[ -w $file ] or [[ -w $file ]] file文件存在且可写时为真
[ -x $file ] or [[ -x $file ]] file文件存在且可执行时为真

    注:在逻辑判断(复合判读中),pattern可以包含元字符,在字符串的判断中,pattern2必须被包含在引号中。

    let命令支持的操作符和C语言中支持的操作符完全相同,如:
    +,-,*,/,%            加,减,乘,除,去模
    >>,<<                右移和左移
    >=,<=,==,!=      大于等于,小于等于,等于,不等于
    &,|,^                  按位与,或,非
    &&,||,!                逻辑与,逻辑或和取反
    还有其含义和C语言等同的快捷操作符,如=,*=,/=,%=,+=,-=,<<=,>>=,&=,|=,^=。

    3.  流程控制语句:
    if语句格式如下:
    #if语句的后面是Shell命令,如果该命令执行成功返回0,则执行then后面的命令。
    if command        
    then
        command
        command
    fi
    #用test命令测试其后面expression的结果,如果为真,则执行then后面的命令。
    if test expression
    then
        command
    fi
    #下面的格式和test expression等同
    if [ string/numeric expression ]
    then
        command
    fi
    #下面的两种格式也可以用于判断语句的条件表达式,而且它们也是目前比较常用的两种。
    if [[ string expression ]]
    then
        command
    fi

    if (( numeric expression ))           #let表达式
    then
        command
    fi
    见如下示例:
    /> cat > test1.sh                       #从命令行直接编辑test1.sh文件。
    echo -e "Are you OK(y/n)? \c"
    read answer
    #这里的$answer变量必须要用双引号扩住,否则判断将失败。当变量$answer等于y或Y时,支持下面的echo命令。
    if [ "$answer" = y -o "$answer" = Y ]   
    then
        echo "Glad to see it."
    fi
    CTRL+D  
    /> . ./test1.sh
    Are you OK(y/n)? y
    Glad to see it.
    上面的判断还可以替换为:
    /> cat > test2.sh
    echo -e "Are you OK(y/n or Maybe)? \c"
    read answer
    # [[ ]]复合命令操作符允许其中的表达式包含元字符,这里输入以y或Y开头的任意单词,或Maybe都执行then后面的echo。
    if [[ $answer == [yY]* || $answer = Maybe ]]  
    then
        echo "Glad to hear it.
    fi
    CTRL+D
    /> . ./test2.sh
    Are you OK(y/n or Maybe)? yes
    Glad to hear it.
    下面的例子将使用Shell中的扩展通配模式。
    /> shopt -s extglob        #打开该扩展模式
    /> answer="not really"
    /> if [[ $answer = [Nn]o?( way |t really) ]]
    > then
    >    echo "I am sorry."
    > fi
    I am sorry.
    对于本示例中的扩展通配符,这里需要给出一个具体的解释。[Nn]o匹配No或no,?( way|t really)则表示0个或1个( way或t really),因此answer变量匹配的字符串为No、no、Not really、not really、No way、no way。
    下面的示例使用了let命令操作符,如:
    /> cat > test3.sh
    if (( $# != 2 ))                    #等同于 [ $# -ne 2 ]
    then
        echo "Usage: $0 arg1 arg2" 1>&2
        exit 1                         #exit退出值为0-255之间,只有0表示成功。
    fi
    if (( $1 < 0 || $1 > 30 ))      #等同于 [ $1 -lt 0 -o $1 -gt 30 ]
    then
        echo "arg1 is out of range."
        exit 2
    fi
    if (( $2 <= 20 ))                  #等同于 [ $2 -le 20 ]
    then
        echo "arg2 is out of range."
    fi
    CTRL+D
    /> sh ./test3.sh
    Usage: ./test3.sh arg1 arg2
    /> echo $?                          #Shell脚本的退出值为exit的参数值。
    1
    /> sh ./test3.sh 40 30
    arg1 is out of range.
    /> echo $?
    2
    下面的示例为如何在if的条件表达式中检验空变量:
    /> cat > test4.sh
    if [ "$name" = "" ]                #双引号就表示空字符串。
    then
        echo "name is null."
    fi
    CTRL+D
    /> . ./test4.sh
    name is null.

    if/elif/else语句的使用方式和if语句极为相似,相信有编程经验的人都不会陌生,这里就不再赘述了,其格式如下:
    if command
    then
        command
    elif command
    then
        command
    else
        command
    fi
    见如下示例脚本:
    /> cat > test5.sh
    echo -e "How old are you? \c"
    read age
    if [ $age -lt 0 -o $age -gt 120 ]                #等同于 (( age < 0 || age > 120 ))
    then
        echo "You are so old."
    elif [ $age -ge 0 -a $age -le 12 ]               #等同于 (( age >= 0 && age <= 12 ))
    then
        echo "You are child."
    elif [ $age -ge 13 -a $age -le 19 ]             #等同于 (( age >= 13 && age <= 19 ))
    then
        echo "You are 13--19 years old."
    elif [ $age -ge 20 -a $age -le 29 ]             #等同于 (( age >= 20 && age <= 29 ))
    then
        echo "You are 20--29 years old."
    elif [ $age -ge 30 -a $age -le 39 ]             #等同于 (( age >= 30 && age <= 39 ))
    then
        echo "You are 30--39 years old."
    else
        echo "You are above 40."
    fi
    CTRL+D
    /> . ./test5.sh
    How old are you? 50
    You are above 40.

    case语句格式如下:
    case variable in
    value1)
        command
        ;;            #相同于C语言中case语句内的break。
    value2)
        command
        ;;
    *)                #相同于C语言中switch语句内的default
       command
        ;;
    esac
    见如下示例脚本:
    /> cat > test6.sh
    #!/bin/sh
    echo -n "Choose a color: "
    read color
    case "$color" in
    [Bb]l??)
        echo "you select blue color."
        ;;
    [Gg]ree*)
        echo "you select green color."
        ;;
    red|orange)
        echo "you select red or orange."
        ;;
    *)
        echo "you select other color."
        ;;
    esac
    echo "Out of case command."
    /> . ./test6.sh
    Choose a color: green
    you select green color.
    Out of case command.

   4.  循环语句:
    Bash Shell中主要提供了三种循环方式:for、while和until。
    for循环声明格式:
    for variable in word_list
    do
        command
    done
    见如下示例脚本:
    /> cat > test7.sh
    for score in math english physics chemist   #for将循环读取in后面的单词列表,类似于Java的for-each。
    do
        echo "score = $score"
    done
    echo "out of for loop"
    CTRL+D
    /> . ./test7.sh
    score = math
    score = english
    score = physics
    score = chemist
    out of for loop

    /> cat > mylist   #构造数据文件
    tom
    patty
    ann
    jake
    CTRL+D
    /> cat > test8.sh
    #!/bin/sh
    for person in $(cat mylist)                 #for将循环读取cat mylist命令的执行结果。
    do
        echo "person = $person"
    done
    echo "out of for loop."
    CTRL+D
    /> . ./test8.sh
    person = tom
    person = patty
    person = ann
    person = jake
    out of for loop.

    /> cat > test9.sh
    for file in test[1-8].sh                        #for将读取test1-test8,后缀为.sh的文件
    do
        if [ -f $file ]                              #判断文件在当前目录是否存在。
        then
            echo "$file exists."
        fi
    done
    CTRL+D
    /> . ./test9.sh
    test2.sh exists.
    test3.sh exists.
    test4.sh exists.
    test5.sh exists.
    test6.sh exists.
    test7.sh exists.
    test8.sh exists.

    /> cat > test10.sh
    for name in $*                                  #读取脚本的命令行参数数组,还可以写成for name的简化形式。
    do
        echo "Hi, $name"
    done
    CTRL+D
    /> . ./test10.sh stephen ann
    Hi, stephen
    Hi, ann

    while循环声明格式:
    while command  #如果command命令的执行结果为0,或条件判断为真时,执行循环体内的命令。
    do
        command
    done
    见如下示例脚本:
    /> cat > test1.sh  
    num=0
    while (( num < 10 ))               #等同于 [ $num -lt 10 ]
    do
        echo -n "$num "
        let num+=1
    done
    echo -e "\nHere's out of loop."
    CTRL+D
    /> . ./test1.sh
    0 1 2 3 4 5 6 7 8 9
    Here's out of loop.

    /> cat > test2.sh
    go=start
    echo Type q to quit.
    while [[ -n $go ]]                     #等同于[ -n "$go" ],如使用该风格,$go需要被双引号括起。
    do
        echo -n How are you.
        read word
        if [[ $word == [Qq] ]]      #等同于[ "$word" = Q -o "$word" = q ]
        then
            echo Bye.
            go=                        #将go变量的值置空。
        fi
    done
    CTRL+D
    /> . ./test2.sh
    How are you. Hi
    How are you. q
    Bye.

    until循环声明格式:
    until command                         #其判断条件和while正好相反,即command返回非0,或条件为假时执行循环体内的命令。
    do
        command
    done
    见如下示例脚本:
    /> cat > test3.sh
    until who | grep stephen           #循环体内的命令将被执行,直到stephen登录,即grep命令的返回值为0时才退出循环。
    do
        sleep 1
        echo "Stephen still doesn't login."
    done
    CTRL+D

    shift命令声明格式:shift [n]
    shift命令用来把脚本的位置参数列表向左移动指定的位数(n),如果shift没有参数,则将参数列表向左移动一位。一旦移位发生,被移出列表的参数就被永远删除了。通常在while循环中,shift用来读取列表中的参数变量。
    见如下示例脚本:
    /> set stephen ann sheryl mark #设置4个参数变量。
    /> shift                                    #向左移动参数列表一次,将stephen移出参数列表。
    /> echo $*
    ann sheryl mark
    /> shift 2                                 #继续向左移动两位,将sheryl和ann移出参数列表
    /> echo $*
    mark
    /> shift 2                                 #继续向左移动两位,由于参数列表中只有mark了,因此本次移动失败。
    /> echo $*
    mark

    /> cat > test4.sh
    while (( $# > 0 ))                    #等同于 [ $# -gt 0 ]
    do
        echo $*
        shift
    done
    CTRL+D
    /> . ./test4.sh a b c d e
    a b c d e
    b c d e
    c d e
    d e
    e        

    break命令声明格式:break [n]
    和C语言不同的是,Shell中break命令携带一个参数,即可以指定退出循环的层数。如果没有指定,其行为和C语言一样,即退出最内层循环。如果指定循环的层数,则退出指定层数的循环体。如果有3层嵌套循环,其中最外层的为1,中间的为2,最里面的是3。
    见如下示例脚本:
    /> cat > test5.sh
    while true
    do
        echo -n "Are you ready to move on?"
        read answer
        if [[ $answer == [Yy] ]]
        then
            break
        else
            echo "Come on."
        fi
    done
    echo "Here we are."
    CTRL+D
    /> . ./test5.sh
    Are you ready to move on? y
    Here we are

    continue命令声明格式:continue [n]
    和C语言不同的是,Shell中continue命令携带一个参数,即可以跳转到指定层级的循环顶部。如果没有指定,其行为和C语言一样,即跳转到最内层循环的顶部。如果指定循环的层数,则跳转到指定层级循环的顶部。如果有3层嵌套循环,其中最外层的为3,中间的为2,最里面的是1。
    /> cat  maillist                       #测试数据文件maillist的内容为以下信息。
    stephen
    ann
    sheryl
    mark

    /> cat > test6.sh
    for name in $(cat maillist)
    do
        if [[ $name == stephen ]]; then
            continue
        else
            echo "Hello, $name."
        fi
    done
    CTRL+D
    /> . ./test6.sh
    Hello, ann.
    Hello, sheryl.
    Hello, mark.

    I/O重新定向和子Shell:
    文件中的输入可以通过管道重新定向给一个循环,输出也可以通过管道重新定向给一个文件。Shell启动一个子Shell来处理I/O重新定向和管道。在循环终止时,循环内部定义的任何变量对于脚本的其他部分来说都是不可见的。
    /> cat > demodata                        #为下面的脚本构造测试数据
    abc
    def
    ghi
    CRTL+D
    /> cat > test7.sh
    if (( $# < 1 ))                                #如果脚本参数的数量小于1,则给出错误提示后退出。
    then
        echo "Usage: $0 filename " >&2
        exit 1
    fi
    count=1
    cat $1 | while read line                   #参数一中的文件被cat命令输出后,通过管道逐行输出给while read line。
    do
        let $((count == 1)) && echo "rocessing file $1..." > /dev/tty  #该行的echo将输出到当前终端窗口。
        echo -e "$count\t$line"              #将输出行号和文件中该行的内容,中间用制表符隔开。
        let count+=1
    done > outfile                               #将while循环中所有的输出,除了>/dev/tty之外,其它的全部输出到outfile文件。
    CTRL+D
    /> . ./test7.sh demodata                #只有一行输出,其余的都输出到outfile中了。
    Processing file demodata...
    /> cat outfile
    1       abc
    2       def
    3       ghi

    /> cat > test8.sh
    for i in 9 7 2 3 5 4
    do
        echo $i
    done | sort -n                                #直接将echo的输出通过管道重定向sort命令。
    CTRL+D
    /> . ./test8.sh
    2
    3
    4
    5
    7
    9

    5.  IFS和循环:
    Shell的内部域分隔符可以是空格、制表符和换行符。它可以作为命令的分隔符用在例如read、set和for等命令中。如果在列表中使用不同的分隔符,用户可以自己定义这个符号。在修改之前将IFS原始符号的值保存在另外一个变量中,这样在需要的时候还可以还原。
    见如下示例脚本:
    /> cat > test9.sh
    names=Stephen:Ann:Sheryl:John   #names变量包含的值用冒号分隔。
    oldifs=$IFS                                   #保留原有IFS到oldifs变量,便于后面的还原。
    IFS=":"                           
    for friends in $names                     #这是遍历以冒号分隔的names变量值。   
    do
        echo Hi $friends
    done
    IFS=$oldifs                                   #将IFS还原为原有的值。
    set Jerry Tom Angela
    for classmates in $*                      #再以原有IFS的值变量参数列表。
    do
        echo Hello $classmates
    done
    CTRL+D
    /> . ./test9.sh
    Hi Stephen
    Hi Ann
    Hi Sheryl
    Hi John
    Hello Jerry
    Hello Tom
    Hello Angela

    6.  函数:
    Shell中函数的职能以及优势和C语言或其它开发语言基本相同,只是语法格式上的一些差异。下面是Shell中使用函数的一些基本规则:
    1) 函数在使用前必须定义。
    2) 函数在当前环境下运行,它和调用它的脚本共享变量,并通过位置参量传递参数。而该位置参量将仅限于该函数,不会影响到脚本的其它地方。
    3) 通过local函数可以在函数内建立本地变量,该变量在出了函数的作用域之后将不在有效。
    4) 函数中调用exit,也将退出整个脚本。
    5) 函数中的return命令返回函数中最后一个命令的退出状态或给定的参数值,该参数值的范围是0-256之间。如果没有return命令,函数将返回最后一个Shell的退出值。
    6) 如果函数保存在其它文件中,就必须通过source或dot命令把它们装入当前脚本。
    7) 函数可以递归。
    将函数从Shell中清空需要执行:unset -f function_name。
    9) 将函数输出到子Shell需要执行:export -f function_name。
    10) 可以像捕捉Shell命令的返回值一样获取函数的返回值,如$(function_name)。
    Shell中函数的声明格式如下:
    function function_name { command; command; }
    见如下示例脚本:
    /> cat > test1.sh
    function increment() {            #定义函数increment。
        local sum                           #定义本地变量sum。
        let "sum=$1+1"   
        return $sum                      #返回值是sum的值。
    }
    echo -n "The num is "
    increment 5                          #increment函数调用。
    echo $?                                #输出increment函数的返回值。
    CTRL+D
    /> . ./test1.sh
    The num is 6

    7.  陷阱信号(trap):
    在Shell程序运行的时候,可能收到各种信号,有的来自于操作系统,有的来自于键盘,而该Shell在收到信号后就立刻终止运行。但是在有些时候,你可能并不希望在信号到达时,程序就立刻停止运行并退出。而是他能希望忽略这个信号而一直在运行,或者在退出前作一些清除操作。trap命令就允许你控制你的程序在收到信号以后的行为。
    其格式如下:
    trap 'command; command' signal-number
    trap 'command; command' signal-name
    trap signal-number  
    trap signal-name
    后面的两种形式主要用于信号复位,即恢复处理该信号的缺省行为。还需要说明的是,如果trap后面的命令是使用单引号括起来的,那么该命令只有在捕获到指定信号时才被执行。如果是双引号,则是在trap设置时就可以执行变量和命令替换了。
    下面是系统给出的信号数字和信号名称的对照表:
    1)SIGHUP 2)SIGINT 3)SIGQUIT 4)SIGILL 5)SIGTRAP 6)SIGABRT 7)SIGBUS SIGFPE
    9)SIGKILL 10) SIGUSR1 11)SIGEGV 12)SIGUSR2 13)SIGPIPE 14)SIGALRM 15)SIGTERM 17)SIGCHLD
    1SIGCONT 19)SIGSTOP ... ...
    见如下示例脚本:
    /> trap 'rm tmp*;exit 1' 1 2 15      #该命令表示在收到信号1、2和15时,该脚本将先执行rm tmp*,然后exit 1退出脚本。
    /> trap 2                                      #当收到信号2时,将恢复为以前的动作,即退出。
    /> trap " " 1 2                               #当收到信号1和2时,将忽略这两个信号。
    /> trap -                                       #表示恢复所有信号处理的原始值。
    /> trap 'trap 2' 2                           #在第一次收到信号2时,执行trap 2,这时将信号2的处理恢复为缺省模式。在收到信号2时,Shell程序退出。
    /> cat > test2.sh
    trap 'echo "Control+C will not terminate $0."' 2   #捕获信号2,即在键盘上按CTRL+C。
    trap 'echo "Control+\ will not terminate $0."' 3   #捕获信号3,即在键盘上按CTRL+\。
    echo "Enter stop to quit shell."
    while true                                                        #无限循环。
    do
        echo -n "Go Go...."
        read
        if [[ $REPLY == [Ss]top ]]                            #直到输入stop或Stop才退出循环和脚本。
       then
            break
        fi
    done
    CTRL+D
    /> . ./test2.sh
    Enter stop to quit shell.
    Go Go....^CControl+C will not terminate -bash.
    ^\Control+\ will not terminate -bash.
    stop

    8.  用getopts处理命令行选项:
    这里的getopts命令和C语言中的getopt几乎是一致的,因为脚本的位置参量在有些时候是失效的,如ls -lrt等。这时候-ltr都会被保存在$1中,而我们实际需要的则是三个展开的选项,即-l、-r和-t。见如下带有getopts的示例脚本:
    /> cat > test3.sh
    #!/bin/sh
    while getopts xy options                           #x和y是合法的选项,并且将-x读入到变量options中,读入时会将x前面的横线去掉。
    do
        case $options in
        x) echo "you entered -x as an option" ;;      
        y) echo "you entered -y as an option" ;;
        esac
    done
    /> ./test3.sh -xy
    you entered -x as an option
    you entered -y as an option
    /> ./test3.sh -x
    you entered -x as an option
    /> ./test3.sh -b                                       #如果输入非法选项,getopts会把错误信息输出到标准错误。
    ./test3.sh: illegal option -- b
    /> ./test3.sh b                                        #该命令不会有执行结果,因为b的前面有没横线,因此是非法选项,将会导致getopts停止处理并退出。

    /> cat > test4.sh
    #!/bin/sh
    while getopts xy options 2>/dev/null         #如果再出现选项错误的情况,该重定向会将错误输出到/dev/null。
    do
        case $options in
        x) echo "you entered -x as an option" ;;
        y) echo "you entered -y as an option" ;;
        \?) echo "Only -x and -y are valid options" 1>&2 # ?表示所有错误的选项,即非-x和-y的选项。
    esac
    done
    /> . ./test4.sh -g                                     #遇到错误的选项将直接执行\?)内的代码。
    Only -x and -y are valid options
    /> . ./test4.sh -xg
    you entered -x as an option
    Only -x and -y are valid options

    /> cat > test5.sh
    #!/bin/sh
    while getopts xyz: arguments 2>/dev/null #z选项后面的冒号用于提示getopts,z选项后面必须有一个参数。
    do
        case $arguments in
        x) echo "you entered -x as an option." ;;
        y) echo "you entered -y as an option." ;;
        z) echo "you entered -z as an option."  #z的后面会紧跟一个参数,该参数保存在内置变量OPTARG中。
            echo "\$OPTARG is $OPTARG.";
           ;;
        \?) echo "Usage opts4 [-xy] [-z argument]"
            exit 1 ;;
        esac
    done
    echo "The number of arguments passed was $(( $OPTIND - 1 ))" #OPTIND保存一下将被处理的选项的位置,他是永远比实际命令行参数多1的数。
    /> ./test5.sh -xyz foo
    you entered -x as an option.
    you entered -y as an option.
    you entered -z as an option.
    $OPTARG is foo.
    The number of arguments passed was 2
    /> ./test5.sh -x -y -z boo
    you entered -x as an option.
    you entered -y as an option.
    you entered -z as an option.
    $OPTARG is boo.
    The number of arguments passed was 4

    9.  eval命令与命令行解析:
    eval命令可以对命令行求值,做Shell替换,并执行命令行,通常在普通命令行解析不能满足要求时使用。
    /> set a b c d
    /> echo The last argument is \$$#
    The last argument is $4
    /> eval echo The last argument is \$$#    #eval命令先进行了变量替换,之后再执行echo命令。
    The last argument is d

作者: compare2000    时间: 2014-05-24 00:17
Linux Shell常用技巧(九) 系统运行进程    1.  进程监控命令(ps):
    要对进程进行监测和控制,首先必须要了解当前进程的情况,也就是需要查看当前进程,而ps命令就是最基本同时也是非常强大的进程查看命令。使用该命令可以 确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等。总之大部分信息都是可以通过执行该命令得到的。
    ps命令存在很多的命令行选项和参数,然而我们最为常用只有两种形式,这里先给出与它们相关的选项和参数的含义:选项 说明
a 显示终端上的所有进程,包括其他用户的进程。
u 以用户为主的格式来显示程序状况。
x 显示所有程序,不以终端来区分。
-e 显示所有进程。
o 其后指定要输出的列,如user,pid等,多个列之间用逗号分隔。
-p 后面跟着一组pid的列表,用逗号分隔,该命令将只是输出这些pid的相关数据。

    /> ps aux

    root         1  0.0  0.1   2828  1400 ?        Ss   09:51   0:02 /sbin/init
    root         2  0.0  0.0      0          0 ?        S    09:51   0:00 [kthreadd]
    root         3  0.0  0.0      0          0 ?        S    09:51   0:00 [migration/0]
    ... ...   
    /> ps -eo user,pid,%cpu,%mem,start,time,command | head -n 4
    USER       PID %CPU %MEM  STARTED     TIME        COMMAND
    root         1         0.0    0.1   09:51:08     00:00:02  /sbin/init
    root         2         0.0    0.0   09:51:08     00:00:00  [kthreadd]
    root         3         0.0    0.0   09:51:08     00:00:00  [migration/0]
    这里需要说明的是,ps中存在很多和进程性能相关的参数,它们均以输出表格中的列的方式显示出来,在这里我们只是给出了非常常用的几个参数,至于更多参数,我们则需要根据自己应用的实际情况去看ps的man手册。
    #以完整的格式显示pid为1(init)的进程的相关数据
    /> ps -fp 1
    UID        PID  PPID  C STIME TTY          TIME   CMD
    root         1        0  0 05:16   ?        00:00:03 /sbin/init
   
    2.  改变进程优先级的命令(nice和renice):
    该Shell命令最常用的使用方式为:nice [-n <优先等级>][执行指令],其中优先等级的范围从-20-19,其中-20最高,19最低,只有系统管理者可以设置负数的等级。
    #后台执行sleep 100秒,同时在启动时将其nice值置为19
    /> nice -n 19 sleep 100 &
    [1] 4661
    #后台执行sleep 100秒,同时在启动时将其nice值置为-19
    /> nice -n -19 sleep 100 &
    [2] 4664
    #关注ps -l输出中用黄色高亮的两行,它们的NI值和我们执行是设置的值一致。
    /> ps -l
    F S   UID   PID  PPID  C PRI  NI  ADDR  SZ    WCHAN  TTY       TIME        CMD
    4 S     0  2833  2829  0  80   0     -      1739     -         pts/2    00:00:00  bash
    0 S     0  4661  2833  0  99  19    -      1066     -         pts/2    00:00:00  sleep
    4 S     0  4664  2833  0  61 -19    -      1066     -         pts/2    00:00:00  sleep
    4 R     0  4665  2833  1  80   0     -      1231     -         pts/2    00:00:00  ps
   
    renice命令主要用于为已经执行的进程重新设定nice值,该命令包含以下几个常用选项:

选项 说明
-g 使用程序群组名称,修改所有隶属于该程序群组的程序的优先权。
-p 改变该程序的优先权等级,此参数为预设值。
-u 指定用户名称,修改所有隶属于该用户的程序的优先权。

    #切换到stephen用户下执行一个后台进程,这里sleep进程将在后台睡眠1000秒。
    /> su stephen
    /> sleep 1000&  
    [1] 4812
    /> exit   #退回到切换前的root用户
    #查看已经启动的后台sleep进程,其ni值为0,宿主用户为stephen
    /> ps -eo user,pid,ni,command | grep stephen
    stephen   4812   0 sleep 1000
    root        4821    0 grep  stephen
    #以指定用户的方式修改该用户下所有进程的nice值
    /> renice -n 5 -u stephen
    500: old priority 0, new priority 5
    #从再次执行ps的输出结果可以看出,该sleep后台进程的nice值已经调成了5
    /> ps -eo user,pid,ni,command | grep stephen
    stephen   4812   5 sleep 1000
    root         4826   0 grep  stephen
    #以指定进程pid的方式修改该进程的nice值
    /> renice -n 10 -p 4812
    4812: old priority 5, new priority 10
    #再次执行ps,该sleep后台进程的nice值已经从5变成了10
    /> ps -eo user,pid,ni,command | grep stephen
    stephen   4812  10 sleep 1000
    root        4829   0 grep  stephen


    3.  列出当前系统打开文件的工具(lsof):
    lsof(list opened files),其重要功能为列举系统中已经被打开的文件,如果没有指定任何选项或参数,lsof则列出所有活动进程打开的所有文件。众所周知,linux环境中任何事物都是文件,如设备、目录、sockets等。所以,用好lsof命令,对日常的linux管理非常有帮助。下面先给出该命令的常用选项:

选项 说明
-a 该选项会使后面选项选出的结果列表进行and操作。
-c command_prefix 显示以command_prefix开头的进程打开的文件。
-p PID 显示指定PID已打开文件的信息
+d directory 从文件夹directory来搜寻(不考虑子目录),列出该目录下打开的文件信息。
+D directory 从文件夹directory来搜寻(考虑子目录),列出该目录下打开的文件信息。
-d num_of_fd 以File Descriptor的信息进行匹配,可使用3-10,表示范围,3,10表示某些值。
-u user 显示某用户的已经打开的文件,其中user可以使用正则表达式。
-i 监听指定的协议、端口、主机等的网络信息,格式为:[proto][@host|addr][:svc_list|port_list]

    #查看打开/dev/null文件的进程。
    /> lsof /dev/null | head -n 5
    COMMAND    PID      USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
    init         1      root    0u   CHR    1,3      0t0 3671 /dev/null
    init         1      root    1u   CHR    1,3      0t0 3671 /dev/null
    init         1      root    2u   CHR    1,3      0t0 3671 /dev/null
    udevd 397      root    0u   CHR    1,3      0t0 3671 /dev/null

    #查看打开22端口的进程
    /> lsof -i:22
    COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
    sshd    1582 root    3u  IPv4  11989      0t0  TCP *:ssh (LISTEN)
    sshd    1582 root    4u  IPv6  11991      0t0  TCP *:ssh (LISTEN)
    sshd    2829 root    3r   IPv4  19635      0t0  TCP bogon:ssh->bogon:15264 (ESTABLISHED)

    #查看init进程打开的文件
    />  lsof -c init
    COMMAND PID USER   FD   TYPE     DEVICE   SIZE/OFF   NODE    NAME
    init               1 root  cwd      DIR        8,2     4096              2        /
    init               1 root  rtd       DIR        8,2     4096              2        /
    init               1 root  txt       REG       8,2   136068       148567     /sbin/init
    init               1 root  mem    REG        8,2    58536      137507     /lib/libnss_files-2.12.so
    init               1 root  mem    REG        8,2   122232     186675     /lib/libgcc_s-4.4.4-20100726.so.1
    init               1 root  mem    REG        8,2   141492     186436     /lib/ld-2.12.so
    init               1 root  mem    REG        8,2  1855584    186631     /lib/libc-2.12.so
    init               1 root  mem    REG        8,2   133136     186632     /lib/libpthread-2.12.so
    init               1 root  mem    REG        8,2    99020      180422     /lib/libnih.so.1.0.0
    init               1 root  mem    REG        8,2    37304      186773     /lib/libnih-dbus.so.1.0.0
    init               1 root  mem    REG        8,2    41728      186633     /lib/librt-2.12.so
    init               1 root  mem    REG        8,2   286380     186634     /lib/libdbus-1.so.3.4.0
    init               1 root    0u     CHR        1,3      0t0           3671      /dev/null
    init               1 root    1u     CHR        1,3      0t0           3671      /dev/null
    init               1 root    2u     CHR        1,3      0t0           3671      /dev/null
    init               1 root    3r      FIFO       0,8      0t0           7969      pipe
    init               1 root    4w     FIFO       0,8      0t0           7969      pipe
    init               1 root    5r      DIR        0,10        0             1         inotify
    init               1 root    6r      DIR        0,10        0             1         inotify
    init               1 root    7u     unix   0xf61e3840  0t0       7970      socket
    init               1 root    9u     unix   0xf3bab280  0t0      11211     socket
    在上面输出的FD列中,显示的是文件的File Descriptor number,或者如下的内容:
    cwd:  current working directory;
    mem:  memory-mapped file;
    mmap: memory-mapped device;
    pd:   parent directory;
    rtd:  root directory;
    txt:  program text (code and data);
    文件的File Descriptor number显示模式有:
    r for read access;
    w for write access;
    u for read and write access;

    在上面输出的TYPE列中,显示的是文件类型,如:
    DIR:  目录
    LINK: 链接文件
    REG:  普通文件


    #查看pid为1的进程(init)打开的文件,其输出结果等同于上面的命令,他们都是init。
    /> lsof -p 1
    #查看owner为root的进程打开的文件。
    /> lsof -u root
    #查看owner不为root的进程打开的文件。
    /> lsof -u ^root
    #查看打开协议为tcp,ip为192.168.220.134,端口为22的进程。
    /> lsof -i tcp@192.168.220.134:22
    COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
    sshd        2829 root     3r    IPv4  19635      0t0      TCP    bogon:ssh->bogon:15264 (ESTABLISHED)   
    #查看打开/root文件夹,但不考虑目录搜寻
    /> lsof +d /root
    #查看打开/root文件夹以及其子目录搜寻
    /> lsof +D /root
    #查看打开FD(0-3)文件的所有进程
    /> lsof -d 0-3
    #-a选项会将+d选项和-c选项的选择结果进行and操作,并输出合并后的结果。
    /> lsof +d .
    COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF  NODE NAME
    bash       9707  root  cwd    DIR    8,1     4096         39887 .
    lsof         9791  root  cwd    DIR    8,1     4096         39887 .
    lsof         9792  root  cwd    DIR    8,1     4096         39887 .
    /> lsof -a -c bash +d .
    COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF  NODE NAME
    bash        9707 root  cwd    DIR    8,1     4096         39887 .
    最后需要额外说明的是,如果在文件名的末尾存在(delete),则说明该文件已经被删除,只是还存留在cache中。


    4.  进程查找/杀掉命令(pgrep/pkill):
    查找和杀死指定的进程, 他们的选项和参数完全相同, 这里只是介绍pgrep。下面是常用的命令行选项:

选项 说明
-d 定义多个进程之间的分隔符, 如果不定义则使用换行符。
-n 表示如果该程序有多个进程正在运行,则仅查找最新的,即最后启动的。
-o 表示如果该程序有多个进程正在运行,则仅查找最老的,即最先启动的。
-G 其后跟着一组group id,该命令在搜索时,仅考虑group列表中的进程。
-u 其后跟着一组有效用户ID(effetive user id),该命令在搜索时,仅考虑该effective user列表中的进程。
-U 其后跟着一组实际用户ID(real user id),该命令在搜索时,仅考虑该real user列表中的进程。
-x 表示进程的名字必须完全匹配, 以上的选项均可以部分匹配。
-l 将不仅打印pid,也打印进程名。
-f 一般与-l合用, 将打印进程的参数。
    #手工创建两个后台进程
    /> sleep 1000&
    3456
    /> sleep 1000&
    3457

    #查找进程名为sleep的进程,同时输出所有找到的pid
    /> pgrep sleep
    3456
    3457
    #查找进程名为sleep的进程pid,如果存在多个,他们之间使用:分隔,而不是换行符分隔。
    /> pgrep -d: sleep
    3456:3457
    #查找进程名为sleep的进程pid,如果存在多个,这里只是输出最后启动的那一个。
    /> pgrep -n sleep
    3457
    #查找进程名为sleep的进程pid,如果存在多个,这里只是输出最先启动的那一个。
    /> pgrep -o  sleep
    3456
    #查找进程名为sleep,同时这个正在运行的进程的组为root和stephen。
    /> pgrep -G root,stephen sleep
    3456
    3457
    #查找有效用户ID为root和oracle,进程名为sleep的进程。
    /> pgrep -u root,oracle sleep
    3456
    3457
    #查找实际用户ID为root和oracle,进程名为sleep的进程。
    /> pgrep -U root,oracle sleep
    3456
    3457
    #查找进程名为sleep的进程,注意这里找到的进程名必须和参数中的完全匹配。
    /> pgrep -x sleep
    3456
    3457
    #-x不支持部分匹配,sleep进程将不会被查出,因此下面的命令没有结果。
    /> pgrep -x sle
    #查找进程名为sleep的进程,同时输出所有找到的pid和进程名。   
    /> pgrep -l sleep
    3456 sleep
    3457 sleep
    #查找进程名为sleep的进程,同时输出所有找到的pid、进程名和启动时的参数。
    /> pgrep -lf sleep
    3456 sleep 1000
    3457 sleep 1000
    #查找进程名为sleep的进程,同时以逗号为分隔符输出他们的pid,在将结果传给ps命令,-f表示显示完整格式,-p显示pid列表,ps将只是输出该列表内的进程数据。
    /> pgrep -f sleep -d, | xargs ps -fp
    UID        PID  PPID  C STIME TTY          TIME CMD
    root      3456  2138  0 06:11 pts/5    00:00:00 sleep 1000
    root      3457  2138  0 06:11 pts/5    00:00:00 sleep 1000
作者: compare2000    时间: 2014-05-26 09:45
细说Linux系统优化-实践篇 .  作为一名linux系统管理员,最主要的工作是优化系统配置,使应用在系统上以最优的状态运行,但是由于硬件问题、软件问题、网络环境等的复杂性 和多变性,导致对系统的优化变得异常复杂,如何定位性能问题出在哪个方面,是性能优化的一大难题, 本章从系统入手,重点讲述由于系统软、硬件配置不当可能造成的性能问题,并且给出了检测系统故障和优化性能的一般方法和流程。
1 cpu性能评估
Cpu是影响Linux性能的主要因素之一,下面先介绍几个查看CPU性能的命令。
1.1 vmstat命令
该命令可以显示关于系统各种资源之间相关性能的简要信息,这里我们主要用它来看CPU的一个负载情况。
下面是vmstat命令在某个系统的输出结果:


点击(此处)折叠或打开
1.[root@node1 ~]# vmstat 2 3
2.procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
3.r b swpd free buff cache si so bi bo in cs us sy id wa st
4.0 0 0 162240 8304 67032 0 0 13 21 1007 23 0 1 98 0 0
5.0 0 0 162240 8304 67032 0 0 1 0 1010 20 0 1 100 0 0
6.0 0 0 162240 8304 67032 0 0 1 1 1009 18 0 1 99 0 0

对上面每项的输出解释如下:
? procs
? r列表示运行和等待cpu时间片的进程数,这个值如果长期大于系统CPU的个数,说明CPU不足,需要增加CPU。
? b列表示在等待资源的进程数,比如正在等待I/O、或者内存交换等。
? memory
? swpd列表示切换到内存交换区的内存数量(以k为单位)。如果swpd的值不为0,或者比较大,只要si、so的值长期为0,这种情况下一般不用担心,不会影响系统性能。
? free列表示当前空闲的物理内存数量(以k为单位)
? buff列表示buffers cache的内存数量,一般对块设备的读写才需要缓冲。
? cache列表示page cached的内存数量,一般作为文件系统cached,频繁访问的文件都会被cached,如果cache值较大,说明cached的文件数较多,如果此时IO中bi比较小,说明文件系统效率比较好。
? swap
? si列表示由磁盘调入内存,也就是内存进入内存交换区的数量。
? so列表示由内存调入磁盘,也就是内存交换区进入内存的数量。
一般情况下,si、so的值都为0,如果si、so的值长期不为0,则表示系统内存不足。需要增加系统内存。
? IO项显示磁盘读写状况
? Bi列表示从块设备读入数据的总量(即读磁盘)(每秒kb)。
? Bo列表示写入到块设备的数据总量(即写磁盘)(每秒kb)
这里我们设置的bi+bo参考值为1000,如果超过1000,而且wa值较大,则表示系统磁盘IO有问题,应该考虑提高磁盘的读写性能。
? system 显示采集间隔内发生的中断数
? in列表示在某一时间间隔中观测到的每秒设备中断数。
? cs列表示每秒产生的上下文切换次数。
上面这2个值越大,会看到由内核消耗的CPU时间会越多。
? CPU项显示了CPU的使用状态,此列是我们关注的重点。
? us列显示了用户进程消耗的CPU 时间百分比。us的值比较高时,说明用户进程消耗的cpu时间多,但是如果长期大于50%,就需要考虑优化程序或算法。
? sy列显示了内核进程消耗的CPU时间百分比。Sy的值较高时,说明内核消耗的CPU资源很多。
根据经验,us+sy的参考值为80%,如果us+sy大于 80%说明可能存在CPU资源不足。
? id 列显示了CPU处在空闲状态的时间百分比。
? wa列显示了IO等待所占用的CPU时间百分比。wa值越高,说明IO等待越严重,根据经验,wa的参考值为20%,如果wa超过20%,说明IO等待严重,引起IO等待的原因可能是磁盘大量随机读写造成的,也可能是磁盘或者磁盘控制器的带宽瓶颈造成的(主要是块操作)。
综上所述,在对CPU的评估中,需要重点注意的是procs项r列的值和CPU项中us、sy和id列的值。
1.2  sar命令
检查CPU性能的第二个工具是sar,sar功能很强大,可以对系统的每个方面进行单独的统计,但是使用sar命令会增加系统开销,不过这些开销是可以评估的,对系统的统计结果不会有很大影响。
下面是sar命令对某个系统的CPU统计输出:


点击(此处)折叠或打开
1.[root@webserver ~]# sar -u 3 5
2.Linux 2.6.9-42.ELsmp (webserver) 11/28/2008 _i686_ (8 CPU)
3.11:41:24 AM CPU %user %nice %system %iowait %steal %idle
4.11:41:27 AM all 0.88 0.00 0.29 0.00 0.00 98.83
5.11:41:30 AM all 0.13 0.00 0.17 0.21 0.00 99.50
6.11:41:33 AM all 0.04 0.00 0.04 0.00 0.00 99.92
7.11:41:36 AM all 0.29 0.00 0.13 0.00 0.00 99.58
8.11:41:39 AM all 0.38 0.00 0.17 0.04 0.00 99.41
9.Average: all 0.34 0.00 0.16 0.05 0.00 99.45

对上面每项的输出解释如下:
? %user列显示了用户进程消耗的CPU 时间百分比。
? %nice列显示了运行正常进程所消耗的CPU 时间百分比。
? %system列显示了系统进程消耗的CPU时间百分比。
? %iowait列显示了IO等待所占用的CPU时间百分比
? %steal列显示了在内存相对紧张的环境下pagein强制对不同的页面进行的steal操作 。
? %idle列显示了CPU处在空闲状态的时间百分比。
这个输出是对系统整体CPU使用状况的统计,每项的输出都非常直观,并且最后一行Average是个汇总行,是上面统计信息的一个平均值。
需要注意的一点是:第一行的统计信息中包含了sar本身的统计消耗,所以%user列的值会偏高一点,不过,这不会对统计结果产生多大影响。
在一个多CPU的系统中,如果程序使用了单线程,会出现这么一个现象,CPU的整体使用率不高,但是系统应用却响应缓慢,这可能是由于程序使用单线程的原因,单线程只使用一个CPU,导致这个CPU占用率为100%,无法处理其它请求,而其它的CPU却闲置,这就导致 了整体CPU使用率不高,而应用缓慢 现象的发生 。
针对这个问题,可以对系统的每个CPU分开查询,统计每个CPU的使用情况:


点击(此处)折叠或打开
1.[root@webserver ~]# sar -P 0 3 5
2.Linux 2.6.9-42.ELsmp (webserver) 11/29/2008 _i686_ (8 CPU)
3.06:29:33 PM CPU %user %nice %system %iowait %steal %idle
4.06:29:36 PM 0 3.00 0.00 0.33 0.00 0.00 96.67
5.06:29:39 PM 0 0.67 0.00 0.33 0.00 0.00 99.00
6.06:29:42 PM 0 0.00 0.00 0.33 0.00 0.00 99.67
7.06:29:45 PM 0 0.67 0.00 0.33 0.00 0.00 99.00
8.06:29:48 PM 0 1.00 0.00 0.33 0.33 0.00 98.34
9.Average: 0 1.07 0.00 0.33 0.07 0.00 98.53
这个输出是对系统的第一颗CPU的信息统计,需要注意的是,sar中对CPU的计数是从0开始的,因此,“sar -P 0 3 5”表示对系统的第一颗CPU进行信息统计,“sar -P 4 3 5”则表示对系统的第五颗CPU进行统计。依次类推。可以看出,上面的系统有八颗CPU。
1.3 iostat命令
iostat指令主要用于统计磁盘IO状态,但是也能查看CPU的使用信息,它的局限性是只能显示系统所有CPU的平均信息,看下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# iostat -c
2.Linux 2.6.9-42.ELsmp (webserver) 11/29/2008 _i686_ (8 CPU)
3.avg-cpu: %user %nice %system %iowait %steal %idle
4.2.52 0.00 0.30 0.24 0.00 96.96
在这里,我们使用了“-c”参数,只显示系统CPU的统计信息,输出中每项代表的含义与sar命令的输出项完全相同,不再详述。
1.4 uptime命令
uptime是监控系统性能最常用的一个命令,主要用来统计系统当前的运行状况,输出的信息依次为:系统现在的时间、系统从上次开机到现在运行了多长时间、系统目前有多少登陆用户、系统在一分钟内、五分钟内、十五分钟内的平均负载。看下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# uptime
2.18:52:11 up 27 days, 19:44, 2 users, load average: 0.12, 0.08, 0.08

这里需要注意的是load average这个输出值,这三个值的大小一般不能大于系统CPU的个数,例如,本输出中系统有8个CPU,如果load average的三个值长期大于8时,说明CPU很繁忙,负载很高,可能会影响系统性能,但是偶尔大于8时,倒不用担心,一般不会影响系统性能。相反,如果load average的输出值小于CPU的个数,则表示CPU还有空闲的时间片,比如本例中的输出,CPU是非常空闲的。
1.5 本节小结
上面介绍了检查CPU使用状况的四个命令,通过这些命令需要了解的是:系统CPU是否出现性能瓶颈,也就是说,以上这些命令只能查看CPU是否繁忙,负载是否过大,但是无法知道CPU为何负载过大,因而,判断系统CPU出现问题后,要结合top、ps等命令进一步检查是由那些进程导致CPU负载过大的。引起CPU资源紧缺的原因可能是应用程序不合理造成的,也可能是硬件资源匮乏引起的,所以,要具体问题具体分析,或者优化应用程序,或者增加系统CPU资源。
2 内存性能评估
内存的管理和优化是系统性能优化的一个重要部分,内存资源的充足与否直接影响应用系统的使用性能,在进行内存优化之前,一定要熟悉linux的内存管理机制,这一点我们在前面的章节已经有深入讲述,本节的重点是如何通过系统命令监控linux系统的内存使用状况。
2.1 free 命令
free是监控linux内存使用状况最常用的指令,看下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# free -m
2.total used free shared buffers cached
3.Mem: 8111 7185 925 0 243 6299
4.-/+ buffers/cache: 643 7468
5.Swap: 8189 0 8189
  “free –m”表示以M为单位查看内存使用情况,在这个输出中,我们重点关注的应该是free列与cached列的输出值,由输出可知,此系统共8G内存,系统空闲内存还有925M,其中,Buffer Cache占用了243M,Page Cache占用了6299M,由此可知系统缓存了很多的文件和目录,而对于应用程序来说,可以使用的内存还有7468M,当然这个7468M包含了Buffer Cache和Page Cache的值。在swap项可以看出,交换分区还未使用。所以从应用的角度来说,此系统内存资源还非常充足。
  一般有这样一个经验公式:应用程序可用内存/系统物理内存>70%时,表示系统内存资源非常充足,不影响系统性能,应用程序可用内存/系统物理内存<20%时,表示系统内存资源紧缺,需要增加系统内存,20%<应用程序可用内存/系统物理内存<70%时,表示系统内存资源基本能满足应用需求,暂时不影响系统性能。
  free命令还可以适时的监控内存的使用状况,使用“-s”参数可以在指定的时间段内不间断的监控内存的使用情况:


点击(此处)折叠或打开
1.[root@webserver ~]# free -b -s 5
2.total used free shared buffers cached
3.Mem: 8505901056 7528706048 977195008 0 260112384 6601158656
4.-/+ buffers/cache: 667435008 7838466048
5.Swap: 8587149312 163840 8586985472
6.total used free shared buffers cached
7.Mem: 8505901056 7526936576 978964480 0 260128768 6601142272
8.-/+ buffers/cache: 665665536 7840235520
9.Swap: 8587149312 163840 8586985472
10.total used free shared buffers cached
11.Mem: 8505901056 7523987456 981913600 0 260141056 6601129984
12.-/+ buffers/cache: 662716416 7843184640
13.Swap: 8587149312 163840 8586985472
  其中,“-b”表示以千字节(也就是1024字节为单位)来显示内存使用情况。
2.2 通过watch与free相结合动态监控内存状况
watch是一个非常有用的命令,几乎每个linux发行版都带有这个工具,通过watch,可以动态的监控命令的运行结果,省去手动执行的麻烦。
  可以在watch后面跟上需要运行的命令,watch就会自动重复去运行这个命令,默认是2秒钟执行一次,并把执行的结果更新在屏幕上。例如:


点击(此处)折叠或打开
1.[root@webserver ~]# watch -n 3 -d free
2.Every 3.0s: free Sun Nov 30 16:23:20 2008
3.total used free shared buffers cached
4.Mem: 8306544 7349548 956996 0 203296 6500024
5.-/+ buffers/cache: 646228 7660316
6.Swap: 8385888 160 8385728

其中,“-n”指定重复执行的时间,“-d”表示高亮显示变动。
2.3 vmstat命令监控内存
vmstat命令在监控系统内存方面功能强大,请看下面的一个输出:


点击(此处)折叠或打开
1.procs -----------memory---------- ---swap-- -----io---- --system-- ----cpu----
2.r b swpd free buff cache si so bi bo in cs us sy id wa
3.0 0 906440 22796 155616 1325496 340 180 2 4 1 4 80 0 10 10
4.0 0 906440 42796 155616 1325496 320 289 0 54 1095 287 70 15 0 15
5.0 0 906440 42884 155624 1325748 236 387 2 102 1064 276 78 2 5 15

对于内存的监控,在vmstat中重点关注的是swpd、si和so行,从这个输出可以看出,此系统内存资源紧缺,swpd占用了900M左右内存,si和so占用很大,而由于系统内存的紧缺,导致出现15%左右的系统等待,此时增加系统的内存是必须要做的。
2.4 sar -r命令组合
sar命令也可以监控linux的内存使用状况,可以通过“sar –r”组合查看系统内存和交换空间的使用率。请看下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# sar -r 2 3
2.Linux 2.6.9-42.ELsmp (webserver) 11/30/2008 _i686_ (8 CPU)
3.09:57:33 PM kbmemfree kbmemused %memused kbbuffers kbcached kbcommit %commit
4.09:57:35 PM 897988 7408556 89.19 249428 6496532 786556 4.71
5.09:57:37 PM 898564 7407980 89.18 249428 6496532 784276 4.70
6.09:57:39 PM 899196 7407348 89.17 249440 6496520 782132 4.69
7.Average: 898583 7407961 89.18 249432 6496528 784321 4.70

其中:
Kbmemfree表示空闲物理内存大小,kbmemused表示已使用的物理内存空间大小,%memused表示已使用内存占总内存大小的百分比,kbbuffers和kbcached分别表示Buffer Cache和Page Cache的大小,kbcommit和%commit分别表示应用程序当前使用的内存大小和使用百分比。
可以看出sar的输出其实与free的输出完全对应,不过sar更加人性化,不但给出了内存使用量,还给出了内存使用的百分比以及统计的平均值。从%commit项可知,此系统目前内存资源充足。
2.5 本节小结
上面介绍了内存监控常用的几个指令以及一些经验规则,其实现在的系统在内存方面出现的瓶颈已经很少,因为内存价格很低,充足的内存已经完全能满足应用程序和系统本身的需要,如果系统在内存方面出现瓶颈,很大的可能是应用程序本身的问题造成的。
3 磁盘I/O性能评估
在对磁盘I/O性能做评估之前,必须知道的几个方面是:
? 熟悉RAID存储方式,可以根据应用的不同,选择不同的RAID方式,例如,如果一个应用经常有大量的读操作,可以选择RAID5方式构建磁盘阵列存储数据,如果应用有大量的、频繁的写操作,可以选择raid0存取方式,如果应用对数据安全要求很高,同时对读写也有要求的话,可以考虑raid01存取方式等等。
? 尽可能用内存的读写代替直接磁盘I/O,使频繁访问的文件或数据放入内存中进行操作处理,因为内存读写操作比直接磁盘读写的效率要高千倍。
? 将经常进行读写的文件与长期不变的文件独立出来,分别放置到不同的磁盘设备上。
? 对于写操作频繁的数据,可以考虑使用裸设备代替文件系统。这里简要讲述下文件系统与裸设备的对比:
使用裸设备的优点有:
? 数据可以直接读写,不需要经过操作系统级的缓存,节省了内存资源,避免了内存资源争用。
? 避免了文件系统级的维护开销,比如文件系统需要维护超级块、I-node等。
? 避免了操作系统的cache预读功能,减少了I/O请求。
使用裸设备的缺点是:
? 数据管理、空间管理不灵活,需要很专业的人来操作。
其实裸设备的优点就是文件系统的缺点,反之也是如此,这就需要我们做出合理的规划和衡量,根据应用的需求,做出对应的策略。
下面接着介绍对磁盘IO的评估标准。
3.1 sar -d命令组合
通过“sar –d”组合,可以对系统的磁盘IO做一个基本的统计,请看下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# sar -d 2 3
2.Linux 2.6.9-42.ELsmp (webserver) 11/30/2008 _i686_ (8 CPU)
3.11:09:33 PM DEV tps rd_sec/s wr_sec/s avgrq-sz avgqu-sz await svctm %util
4.11:09:35 PM dev8-0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
5.11:09:35 PM DEV tps rd_sec/s wr_sec/s avgrq-sz avgqu-sz await svctm %util
6.11:09:37 PM dev8-0 1.00 0.00 12.00 12.00 0.00 0.00 0.00 0.00
7.11:09:37 PM DEV tps rd_sec/s wr_sec/s avgrq-sz avgqu-sz await svctm %util
8.11:09:39 PM dev8-0 1.99 0.00 47.76 24.00 0.00 0.50 0.25 0.05
9.Average: DEV tps rd_sec/s wr_sec/s avgrq-sz avgqu-sz await svctm %util
10.Average: dev8-0 1.00 0.00 19.97 20.00 0.00 0.33 0.17 0.02

对上面每项的输出解释如下:
? DEV表示磁盘设备名称。
? tps表示每秒到物理磁盘的传送数,也就是每秒的I/O流量。一个传送就是一个I/O请求,多个逻辑请求可以被合并为一个物理I/O请求。
? rd_sec/s表示每秒从设备读取的扇区数(1扇区=512字节)。
? wr_sec/s表示每秒写入设备的扇区数目。
? avgrq-sz表示平均每次设备I/O操作的数据大小(以扇区为单位)。
? avgqu-sz表示平均I/O队列长度。
? await表示平均每次设备I/O操作的等待时间(以毫秒为单位)。
? svctm表示平均每次设备I/O操作的服务时间(以毫秒为单位)。
? %util表示一秒中有百分之几的时间用于I/O操作。
Linux中I/O请求系统与现实生活中超市购物排队系统有很多类似的地方,通过对超市购物排队系统的理解,可以很快掌握linux中I/O运行机制。比如:
avgrq-sz类似与超市排队中每人所买东西的多少。
avgqu-sz类似与超市排队中单位时间内平均排队的人数。
await类似与超市排队中每人的等待时间。
svctm类似与超市排队中收银员的收款速度。
%util类似与超市收银台前有人排队的时间比例。
对以磁盘IO性能,一般有如下评判标准:
正常情况下svctm应该是小于await值的,而svctm的大小和磁盘性能有关,CPU、内存的负荷也会对svctm值造成影响,过多的请求也会间接的导致svctm值的增加。
await值的大小一般取决与svctm的值和I/O队列长度以及I/O请求模式,如果svctm的值与await很接近,表示几乎没有I/O等待,磁盘性能很好,如果await的值远高于svctm的值,则表示I/O队列等待太长,系统上运行的应用程序将变慢,此时可以通过更换更快的硬盘来解决问题。
%util项的值也是衡量磁盘I/O的一个重要指标,如果%util接近100%,表示磁盘产生的I/O请求太多,I/O系统已经满负荷的在工作,该磁盘可能存在瓶颈。长期下去,势必影响系统的性能,可以通过优化程序或者通过更换更高、更快的磁盘来解决此问题。
3.2 iostat –d命令组合
通过“iostat –d”命令组合也可以查看系统磁盘的使用状况,请看如下输出:




点击(此处)折叠或打开
1.[root@webserver ~]# iostat -d 2 3
2.Linux 2.6.9-42.ELsmp (webserver) 12/01/2008 _i686_ (8 CPU)
3.Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
4.sda 1.87 2.58 114.12 6479462 286537372
5.Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
6.sda 0.00 0.00 0.00 0 0
7.Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
8.sda 1.00 0.00 12.00 0 24

对上面每项的输出解释如下:
? Blk_read/s表示每秒读取的数据块数。
? Blk_wrtn/s表示每秒写入的数据块数。
? Blk_read表示读取的所有块数
? Blk_wrtn表示写入的所有块数。
这里需要注意的一点是:上面输出的第一项是系统从启动以来到统计时的所有传输信息,从第二次输出的数据才代表在检测的时间段内系统的传输值。
可以通过Blk_read/s和Blk_wrtn/s的值对磁盘的读写性能有一个基本的了解,如果Blk_wrtn/s值很大,表示磁盘的写操作很频繁,可以考虑优化磁盘或者优化程序,如果Blk_read/s值很大,表示磁盘直接读取操作很多,可以将读取的数据放入内存中进行操作。对于这两个选项的值没有一个固定的大小,根据系统应用的不同,会有不同的值,但是有一个规则还是可以遵循的:长期的、超大的数据读写,肯定是不正常的,这种情况一定会影响系统性能。
“iostat –x”组合还提供了对每个磁盘的单独统计,如果不指定磁盘,默认是对所有磁盘进行统计,请看下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# iostat -x /dev/sda 2 3
2.Linux 2.6.9-42.ELsmp (webserver) 12/01/2008 _i686_ (8 CPU)
3.avg-cpu: %user %nice %system %iowait %steal %idle
4.2.45 0.00 0.30 0.24 0.00 97.03
5.Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
6.sda 0.01 12.48 0.10 1.78 2.58 114.03 62.33 0.07 38.39 1.30 0.24
7.avg-cpu: %user %nice %system %iowait %steal %idle
8.3.97 0.00 1.83 8.19 0.00 86.14
9.Device:rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
10.sda 0.00 195.00 0.00 18.00 0.00 1704.00 94.67 0.04 2.50 0.11 0.20
11.avg-cpu: %user %nice %system %iowait %steal %idle
12.4.04 0.00 1.83 8.01 0.00 86.18
13.Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
14.sda 0.00 4.50 0.00 7.00 0.00 92.00 13.14 0.01 0.79 0.14 0.10

这个输出基本与“sar –d”相同,需要说明的几个选项的含义为:
? rrqm/s表示每秒进行merged的读操作数目。
? wrqm/s表示每秒进行 merge 的写操作数目。
? r/s表示每秒完成读I/O设备的次数。
? w/s表示每秒完成写I/O设备的次数。
? rsec/s表示每秒读取的扇区数。
? wsec/s表示每秒写入的扇区数。
3.3 vmstat –d组合
通过“vmstat –d”组合也可以查看磁盘的统计数据,情况下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# vmstat -d 3 2|grep sda
2.disk- ------------reads------------ ------------writes----------- -----IO------
3.total merged sectors ms total merged sectors ms cur sec
4.sda 239588 29282 6481862 1044442 4538678 32387680 295410812 186025580 0 6179
5.disk- ------------reads------------ ------------writes----------- -----IO------
6.total merged sectors ms total merged sectors ms cur sec
7.sda 239588 29282 6481862 1044442 4538680 32387690 295410908 186025581 0 6179

这个输出显示了磁盘的reads、writes和IO的使用状况。
3.4 本节小结
上面主要讲解了对磁盘I/O的性能评估,其实衡量磁盘I/O好坏是多方面的,有应用程序本身的,也有硬件设计上的,还有系统自身配置的问题等,要解决I/O的瓶颈,关键是要提高I/O子系统的执行效率。例如,首要要从应用程序上对磁盘读写进行优化,能够放到内存执行的操作,尽量不要放到磁盘,同时对磁盘存储方式进行合理规划,选择适合自己的RAID存取方式,最后,在系统级别上,可以选择适合自身应用的文件系统,必要时使用裸设备提高读写性能。
4 网络性能评估
网络性能的好坏直接影响应用程序对外提供服务的稳定性和可靠性,监控网络性能,可以从以下几个方面进行管理和优化。
4.1 通过ping命令检测网络的连通性
如果发现网络反应 缓慢,或者连接中断,可以通过ping来测试网络的连通情况,请看下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# ping 10.10.1.254
2.PING 10.10.1.254 (10.10.1.254) 56(84) bytes of data.
3.64 bytes from 10.10.1.254: icmp_seq=0 ttl=64 time=0.235 ms
4.64 bytes from 10.10.1.254: icmp_seq=1 ttl=64 time=0.164 ms
5.64 bytes from 10.10.1.254: icmp_seq=2 ttl=64 time=0.210 ms
6.64 bytes from 10.10.1.254: icmp_seq=3 ttl=64 time=0.178 ms
7.64 bytes from 10.10.1.254: icmp_seq=4 ttl=64 time=0.525 ms
8.64 bytes from 10.10.1.254: icmp_seq=5 ttl=64 time=0.571 ms
9.64 bytes from 10.10.1.254: icmp_seq=6 ttl=64 time=0.220 ms
10.--- 10.10.1.254 ping statistics ---
11.7 packets transmitted, 7 received, 0% packet loss, time 6000ms
12.rtt min/avg/max/mdev = 0.164/0.300/0.571/0.159 ms, pipe 2



在这个输出中,time值显示了两台主机之间的网络延时情况,如果此值很大,则表示网络的延时很大,单位为毫秒。在这个输出的最后,是对上面输出信息的一个总结,packet loss表示网络的丢包率,此值越小,表示网络的质量越高。
4.2 通过netstat –i组合检测网络接口状况
netstat命令提供了网络接口的详细信息,请看下面的输出:


点击(此处)折叠或打开
1.[root@webserver ~]# netstat -i
2.Kernel Interface table
3.Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
4.eth0 1500 0 1313129253 0 0 0 1320686497 0 0 0 BMRU
5.eth1 1500 0 494902025 0 0 0 292358810 0 0 0 BMRU
6.lo 16436 0 41901601 0 0 0 41901601 0 0 0 LRU

对上面每项的输出解释如下:
? Iface表示网络设备的接口名称。
? MTU表示最大传输单元,单位字节。
? RX-OK/TX-OK表示已经准确无误的接收/发送了多少数据包。
? RX-ERR/TX-ERR表示接收/发送数据包时产生了多少错误。
? RX-DRP/TX-DRP表示接收/发送数据包时丢弃了多少数据包。
? RX-OVR/TX-OVR表示由于误差而遗失了多少数据包。
? Flg表示接口标记,其中:
? L:表示该接口是个回环设备。
? B:表示设置了广播地址。
? M:表示接收所有数据包。
? R:表示接口正在运行。
? U:表示接口处于活动状态。
? O:表示在该接口上禁用arp。
? P:表示一个点到点的连接。
正常情况下,RX-ERR/TX-ERR、RX-DRP/TX-DRP和RX-OVR/TX-OVR的值都应该为0,如果这几个选项的值不为0,并且很大,那么网络质量肯定有问题,网络传输性能也一定会下降。
当网络传输存在问题是,可以检测网卡设备是否存在故障,如果可能,可以升级为千兆网卡或者光纤网络,还可以检查网络部署环境是否合理。
4.3 通过netstat –r组合检测系统的路由表信息
在网络不通,或者网络异常时,首先想到的就是检查系统的路由表信息,“netstat –r”的输出结果与route命令的输出完全相同,请看下面的一个实例:


点击(此处)折叠或打开
1.[root@webserver ~]# netstat -r
2.Kernel IP routing table
3.Destination Gateway Genmask Flags MSS Window irtt Iface
4.10.10.1.0 * 255.255.255.0 U 0 0 0 eth0
5.192.168.200.0 * 255.255.255.0 U 0 0 0 eth1
6.169.254.0.0 * 255.255.0.0 U 0 0 0 eth1
7.default 10.10.1.254 0.0.0.0 UG 0 0 0 eth0

关于输出中每项的具体含义,已经在前面章节进行过详细介绍,这里不再多讲,这里我们重点关注的是default行对应的值,default项表示系统的默认路由,对应的网络接口为eth0。

4.4 通过sar –n组合显示系统的网络运行状态
sar提供四种不同的选项来显示网络统计信息,通过“-n”选项可以指定4个不同类型的开关:DEV、EDEV、SOCK和FULL。DEV显示网络接口信息,EDEV显示关于网络错误的统计数据,SOCK显示套接字信息,FULL显示所有三个开关。请看下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# sar -n DEV 2 3
2.Linux 2.6.9-42.ELsmp (webserver) 12/01/2008 _i686_ (8 CPU)
3.02:22:31 PM IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s
4.02:22:33 PM lo 31.34 31.34 37.53 37.53 0.00 0.00 0.00
5.02:22:33 PM eth0 199.50 279.60 17.29 344.12 0.00 0.00 0.00
6.02:22:33 PM eth1 5.47 4.98 7.03 0.36 0.00 0.00 0.00
7.02:22:33 PM sit0 0.00 0.00 0.00 0.00 0.00 0.00 0.00
8.02:22:33 PM IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s
9.02:22:35 PM lo 67.66 67.66 74.34 74.34 0.00 0.00 0.00
10.02:22:35 PM eth0 159.70 222.39 19.74 217.16 0.00 0.00 0.00
11.02:22:35 PM eth1 3.48 4.48 0.44 0.51 0.00 0.00 0.00
12.02:22:35 PM sit0 0.00 0.00 0.00 0.00 0.00 0.00 0.00
13.02:22:35 PM IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s
14.02:22:37 PM lo 4.52 4.52 9.25 9.25 0.00 0.00 0.00
15.02:22:37 PM eth0 102.51 133.67 20.67 116.14 0.00 0.00 0.00
16.02:22:37 PM eth1 27.14 67.34 2.42 89.26 0.00 0.00 0.00
17.02:22:37 PM sit0 0.00 0.00 0.00 0.00 0.00 0.00 0.00
18.Average: IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s
19.Average: lo 34.61 34.61 40.48 40.48 0.00 0.00 0.00
20.Average: eth0 154.08 212.15 19.23 226.17 0.00 0.00 0.00
21.Average: eth1 11.98 25.46 3.30 29.85 0.00 0.00 0.00
22.Average: sit0 0.00 0.00 0.00 0.00 0.00 0.00 0.00

对上面每项的输出解释如下:
? IFACE表示网络接口设备。
? rxpck/s表示每秒钟接收的数据包大小。
? txpck/s表示每秒钟发送的数据包大小。
? rxkB/s表示每秒钟接收的字节数。
? txkB/s表示每秒钟发送的字节数。
? rxcmp/s表示每秒钟接收的压缩数据包。
? txcmp/s表示每秒钟发送的压缩数据包。
? rxmcst/s表示每秒钟接收的多播数据包。
通过“sar –n”的输出,可以清楚的显示网络接口发送、接收数据的统计信息。此外还可以通过“sar -n EDEV 2 3”来统计网络错误信息等。
4.5 小结
本节通过几个常用的网络命令介绍了对网络性能的评估,事实上,网络问题是简单而且容易处理的,只要我们根据上面给出的命令,一般都能迅速定位问题。解决问题的方法一般是增加网络带宽,或者优化网络部署环境。
除了上面介绍的几个命令外,排查网络问题经常用到的命令还有traceroute,主要用于跟踪数据包的传输路径,还有nslookup命令,主要用于判断DNS解析信息。
作者: compare2000    时间: 2014-05-27 11:11
本帖最后由 compare2000 于 2014-05-28 11:09 编辑

reset命令有3种方式:


1.git reset –mixed:此为默认方式,不带任何参数的git reset,即时这种方式,它回退到某个版本,只保留源码,回退commit和index信息

2.git reset –soft:回退到某个版本,只回退了commit的信息,不会恢复到index file一级。如果还要提交,直接commit即可

3.git reset –hard:彻底回退到某个版本,本地的源码也会变为上一个版本的内容




以下是一些reset的示例:

(1) 回退所有内容到上一个版本   
git reset HEAD^   
(2) 回退a.py这个文件的版本到上一个版本   
git reset HEAD^ a.py   
(3) 向前回退到第3个版本   
git reset –soft HEAD~3   
(4) 将本地的状态回退到和远程的一样   
git reset –hard origin/master   
(5) 回退到某个版本   
git reset 057d   
(7) 回退到上一次提交的状态,按照某一次的commit完全反向的进行一次commit   
git revert HEAD   



如果我们某次修改了某些内容,并且已经commit到本地仓库,而且已经push到远程仓库了


这种情况下,我们想把本地和远程仓库都回退到某个版本,该怎么做呢?


前面讲到的git reset只是在本地仓库中回退版本,而远程仓库的版本不会变化


这样,即时本地reset了,但如果再git pull,那么,远程仓库的内容又会和本地之前版本的内容进行merge


这并不是我们想要的东西,这时可以有2种办法来解决这个问题:


1.直接在远程server的仓库目录下,执行git reset –soft 10efa来回退。注意:在远程不能使用mixed或hard参数

2.在本地直接把远程的master分支给删除,然后再把reset后的分支内容给push上去,如下:
        (1)新建old_master分支做备份   
            git branch old_master   
        (2)push到远程   
            git push origin old_masterld_master   
        (3)本地仓库回退到某个版本   
             git reset –hard bae168   
        (4)删除远程的master分支   
             git push origin :master   
        (5)重新创建master分支   
           git push origin master   

在删除远程master分支时,可能会有问题,见下:

        $ git push origin :master  


        error: By default, deleting the current branch is denied, because the next   


        error: 'git clone' won't result in any file checked out, causing confusion.   


        error:   


         error: You can set 'receive.denyDeleteCurrent' configuration variable to   


         error: 'warn' or 'ignore' in the remote repository to allow deleting the   


         error: current branch, with or without a warning message.   


         error:   


         t">error: To squelch this message, you can set it to 'refuse'.   


        error: refusing to delete the current branch: refs/heads/master   


        To git@xx.sohu.com:gitosis_test   


        ! [remote rejected] master (deletion of the current branch prohibited)   


        error: failed to push some refs to 'git@xx.sohu.com:gitosis_test'   


这时需要在远程仓库目录下,设置git的receive.denyDeleteCurrent参数

git receive.denyDeleteCurrent warn   



然后,就可以删除远程的master分支了


虽然说有以上2种方法可以回退远程分支的版本,但这2种方式,都挺危险的,需要谨慎操作……





reference:http://blog.163.com/jianlizhao@1 ... 163201183045856216/

我们都是打tag,没那么做过,这个命令应该能做到repo forall -c 'HAHA=`git log --before="3 days" -1 --pretty=format:"%H"`;git reset --hard $HAHA'  

http://www.csdn.net/article/2014-05-27/2819954
很幸运自己能够在不同类型的单位工作过,也因此接触了很多不同背景、不同思维习惯、不同性格类型的人,积累了人脉,同时也丰富了自己的人生阅历。中小型私企是我职业生涯的第一步,我打下了坚实的基础,不仅仅是技术,更重要的是使自己的综合素质得到了提高。而大型外企的管理等各个方面相对比较正规,有一整套的流程,我懂得了如何带领和管理一个大型团队。而在事业单位,我学会了如何跟不同类型的人沟通交流。这些年以来,我渐渐感悟出一点对生活的理解:程序员不仅要关注自己所处领域的技术,更要关注我们所处的环境。技术可能不会做一辈子,锻炼自己的生存能力则是最现实的话题。
编程是个技术活,既然是技术活就需要不断的在实践当中加以练习。因为我之前自学过C语言,算是有一点基础,但是这与真正的软件开发还是有相当的差距。为了能够尽快的掌握C#开发,我买了很多相关的技术书籍。入门经典、高级编程之类的书都买了,一开始的时候就跟着上面一个一个例子不断在VS编译器中调试、运行,监控每一步值的变化,慢慢培养编程的感觉。掌握了C#语言之后,后来我喜欢做一些小工具之类练习,比如我的浏览器(Webbrowser)、记账单(单机版Access应用)等等。通过这些一个又一个的小工具开发练习,我慢慢的掌握了软件开发的技巧。更重要的是通过这些编程练习,我建立起了肯定能学好编程的信心,并从中找到了乐趣以及成就感
作者: compare2000    时间: 2014-06-24 16:50
跟我一起写 Makefile


陈皓 (CSDN)

概述
——

什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作,但我觉得要作一个好的和professional的程序员,makefile还是要懂。这就好像现在有这么多的HTML的编辑器,但如果你想成为一个专业人士,你还是要了解HTML的标识的含义。特别在Unix下的软件编译,你就不能不自己写makefile了,会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。

因为,makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。

makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。

现在讲述如何写makefile的文章比较少,这是我想写这篇文章的原因。当然,不同产商的make各不相同,也有不同的语法,但其本质都是在“文件依赖性”上做文章,这里,我仅对GNU的make进行讲述,我的环境是RedHat Linux 8.0,make的版本是3.80。必竟,这个make是应用最为广泛的,也是用得最多的。而且其还是最遵循于IEEE 1003.2-1992 标准的(POSIX.2)。

在这篇文档中,将以C/C++的源码作为我们基础,所以必然涉及一些关于C/C++的编译的知识,相关于这方面的内容,还请各位查看相关的编译器的文档。这里所默认的编译器是UNIX下的GCC和CC。



关于程序的编译和链接
——————————

在此,我想多说关于程序编译的一些规范和方法,一般来说,无论是C、C++、还是pas,首先要把源文件编译成中间代码文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。

编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(O文件或是OBJ文件)。

链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。

总结一下,源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error),在VC下,这种错误一般是:Link 2001错误,意思说是说,链接器未能找到函数的实现。你需要指定函数的Object File.

好,言归正传,GNU的make有许多的内容,闲言少叙,还是让我们开始吧。



Makefile 介绍
———————

make命令执行时,需要一个 Makefile 文件,以告诉make命令需要怎么样的去编译和链接程序。

首先,我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNU的make使用手册,在这个示例中,我们的工程有8个C文件,和3个头文件,我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是:
1)如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
2)如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。

只要我们的Makefile写得够好,所有的这一切,我们只用一个make命令就可以完成,make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序。


一、Makefile的规则

在讲述这个Makefile之前,还是让我们先来粗略地看一看Makefile的规则。

target ... : prerequisites ...
command
...
...

target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。

prerequisites就是,要生成那个target所需要的文件或是目标。

command也就是make需要执行的命令。(任意的Shell命令)

这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。

说到底,Makefile的东西就是这样一点,好像我的这篇文档也该结束了。呵呵。还不尽然,这是Makefile的主线和核心,但要写好一个Makefile还不够,我会以后面一点一点地结合我的工作经验给你慢慢到来。内容还多着呢。:)


二、一个示例

正如前面所说的,如果一个工程有3个头文件,和8个C文件,我们为了完成前面所述的那三个规则,我们的Makefile应该是下面的这个样子的。

edit : main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o

main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o

反斜杠(/)是换行符的意思。这样比较便于Makefile的易读。我们可以把这个内容保存在文件为“Makefile”或“makefile”的文件中,然后在该目录下直接输入命令“make”就可以生成执行文件edit。如果要删除执行文件和所有的中间目标文件,那么,只要简单地执行一下“make clean”就可以了。

在这个makefile中,目标文件(target)包含:执行文件edit和中间目标文件(*.o),依赖文件(prerequisites)就是冒号后面的那些 .c 文件和 .h文件。每一个 .o 文件都有一组依赖文件,而这些 .o 文件又是执行文件 edit 的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的,换言之,目标文件是哪些文件更新的。

在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个Tab键作为开头。记住,make并不管命令是怎么工作的,他只管执行所定义的命令。make会比较targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的话,那么,make就会执行后续定义的命令。

这里要说明一点的是,clean不是一个文件,它只不过是一个动作名字,有点像C语言中的lable一样,其冒号后什么也没有,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。



三、make是如何工作的

在默认的方式下,也就是我们只输入make命令。那么,

1、make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
2、如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。
3、如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
4、如果edit所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
5、当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件edit了。

这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。

通过上述分析,我们知道,像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令——“make clean”,以此来清除所有的目标文件,以便重编译。

于是在我们编程中,如果这个工程已被编译过了,当我们修改了其中一个源文件,比如file.c,那么根据我们的依赖性,我们的目标file.o会被重编译(也就是在这个依性关系后面所定义的命令),于是file.o的文件也是最新的啦,于是file.o的文件修改时间要比edit要新,所以edit也会被重新链接了(详见edit目标文件后定义的命令)。

而如果我们改变了“command.h”,那么,kdb.o、command.o和files.o都会被重编译,并且,edit会被重链接。


四、makefile中使用变量

在上面的例子中,先让我们看看edit的规则:

edit : main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o

我们可以看到[.o]文件的字符串被重复了两次,如果我们的工程需要加入一个新的[.o]文件,那么我们需要在两个地方加(应该是三个地方,还有一个地方在clean中)。当然,我们的makefile并不复杂,所以在两个地方加也不累,但如果makefile变得复杂,那么我们就有可能会忘掉一个需要加入的地方,而导致编译失败。所以,为了makefile的易维护,在makefile中我们可以使用变量。makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好。

比如,我们声明一个变量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ,反正不管什么啦,只要能够表示obj文件就行了。我们在makefile一开始就这样定义:

objects = main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o

于是,我们就可以很方便地在我们的makefile中以“$(objects)”的方式来使用这个变量了,于是我们的改良版makefile就变成下面这个样子:

objects = main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o

edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)


于是如果有新的 .o 文件加入,我们只需简单地修改一下 objects 变量就可以了。

关于变量更多的话题,我会在后续给你一一道来。


五、让make自动推导

GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为,我们的make会自动识别,并自己推导命令。

只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果make找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来,于是,我们的makefile再也不用写得这么复杂。我们的是新的makefile又出炉了。


objects = main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o

edit : $(objects)
cc -o edit $(objects)

main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h

.PHONY : clean
clean :
rm edit $(objects)

这种方法,也就是make的“隐晦规则”。上面文件内容中,“.PHONY”表示,clean是个伪目标文件。

关于更为详细的“隐晦规则”和“伪目标文件”,我会在后续给你一一道来。


六、另类风格的makefile

即然我们的make可以自动推导命令,那么我看到那堆[.o]和[.h]的依赖就有点不爽,那么多的重复的[.h],能不能把其收拢起来,好吧,没有问题,这个对于make来说很容易,谁叫它提供了自动推导命令和文件的功能呢?来看看最新风格的makefile吧。

objects = main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o

edit : $(objects)
cc -o edit $(objects)

$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h

.PHONY : clean
clean :
rm edit $(objects)

这种风格,让我们的makefile变得很简单,但我们的文件依赖关系就显得有点凌乱了。鱼和熊掌不可兼得。还看你的喜好了。我是不喜欢这种风格的,一是文件的依赖关系看不清楚,二是如果文件一多,要加入几个新的.o文件,那就理不清楚了。

七、清空目标文件的规则

每个Makefile中都应该写一个清空目标文件(.o和执行文件)的规则,这不仅便于重编译,也很利于保持文件的清洁。这是一个“修养”(呵呵,还记得我的《编程修养》吗)。一般的风格都是:

clean:
rm edit $(objects)

更为稳健的做法是:

.PHONY : clean
clean :
-rm edit $(objects)

前面说过,.PHONY意思表示clean是一个“伪目标”,。而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,clean的规则不要放在文件的开头,不然,这就会变成make的默认目标,相信谁也不愿意这样。不成文的规矩是——“clean从来都是放在文件的最后”。


上面就是一个makefile的概貌,也是makefile的基础,下面还有很多makefile的相关细节,准备好了吗?准备好了就来。

Makefile 总述
———————

一、Makefile里有什么?

Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。

1、显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。

2、隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。

3、变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。

4、文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。

5、注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“/#”。

最后,还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始。


二、Makefile的文件名

默认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到了解释这个文件。在这三个文件名中,最好使用“Makefile”这个文件名,因为,这个文件名第一个字符为大写,这样有一种显目的感觉。最好不要用“GNUmakefile”,这个文件是GNU的make识别的。有另外一些make只对全小写的“makefile”文件名敏感,但是基本上来说,大多数的make都支持“makefile”和“Makefile”这两种默认文件名。

当然,你可以使用别的文件名来书写Makefile,比如:“Make.Linux”,“Make.Solaris”,“Make.AIX”等,如果要指定特定的Makefile,你可以使用make的“-f”和“--file”参数,如:make -f Make.Linux或make --file Make.AIX。


三、引用其它的Makefile

在Makefile使用include关键字可以把别的Makefile包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。include的语法是:

include <filename>

filename可以是当前操作系统Shell的文件模式(可以保含路径和通配符)

在include前面可以有一些空字符,但是绝不能是[Tab]键开始。include和<filename>可以用一个或多个空格隔开。举个例子,你有这样几个Makefile:a.mk、b.mk、c.mk,还有一个文件叫foo.make,以及一个变量$(bar),其包含了e.mk和f.mk,那么,下面的语句:

include foo.make *.mk $(bar)

等价于:

include foo.make a.mk b.mk c.mk e.mk f.mk

make命令开始时,会把找寻include所指出的其它Makefile,并把其内容安置在当前的位置。就好像C/C++的#include指令一样。如果文件都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:

1、如果make执行时,有“-I”或“--include-dir”参数,那么make就会在这个参数所指定的目录下去寻找。
2、如果目录<prefix>/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。

如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号“-”。如:

-include <filename>
其表示,无论include过程中出现什么错误,都不要报错继续执行。和其它版本make兼容的相关命令是sinclude,其作用和这一个是一样的。


四、环境变量 MAKEFILES

如果你的当前环境中定义了环境变量MAKEFILES,那么,make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和include不同的是,从这个环境变中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也会不理。

但是在这里我还是建议不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make时,所有的Makefile都会受到它的影响,这绝不是你想看到的。在这里提这个事,只是为了告诉大家,也许有时候你的Makefile出现了怪事,那么你可以看看当前环境中有没有定义这个变量。


五、make的工作方式

GNU的make工作时的执行步骤入下:(想来其它的make也是类似)

1、读入所有的Makefile。
2、读入被include的其它Makefile。
3、初始化文件中的变量。
4、推导隐晦规则,并分析所有规则。
5、为所有的目标文件创建依赖关系链。
6、根据依赖关系,决定哪些目标要重新生成。
7、执行生成命令。

1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。

当然,这个工作方式你不一定要清楚,但是知道这个方式你也会对make更为熟悉。有了这个基础,后续部分也就容易看懂了。



书写规则
————

规则包含两个部分,一个是依赖关系,一个是生成目标的方法。

在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有一个最终目标,其它的目标都是被这个目标所连带出来的,所以一定要让make知道你的最终目标是什么。一般来说,定义在Makefile中的目标可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么,第一个目标会成为最终的目标。make所完成的也就是这个目标。

好了,还是让我们来看一看如何书写规则。


一、规则举例

foo.o : foo.c defs.h # foo模块
cc -c -g foo.c

看到这个例子,各位应该不是很陌生了,前面也已说过,foo.o是我们的目标,foo.c和defs.h是目标所依赖的源文件,而只有一个命令“cc -c -g foo.c”(以Tab键开头)。这个规则告诉我们两件事:

1、文件的依赖关系,foo.o依赖于foo.c和defs.h的文件,如果foo.c和defs.h的文件日期要比foo.o文件日期要新,或是foo.o不存在,那么依赖关系发生。
2、如果生成(或更新)foo.o文件。也就是那个cc命令,其说明了,如何生成foo.o这个文件。(当然foo.c文件include了defs.h文件)


二、规则的语法

targets : prerequisites
command
...

或是这样:

targets : prerequisites ; command
command
...

targets是文件名,以空格分开,可以使用通配符。一般来说,我们的目标基本上是一个文件,但也有可能是多个文件。

command是命令行,如果其不与“targetrerequisites”在一行,那么,必须以[Tab键]开头,如果和prerequisites在一行,那么可以用分号做为分隔。(见上)

prerequisites也就是目标所依赖的文件(或依赖目标)。如果其中的某个文件要比目标文件要新,那么,目标就被认为是“过时的”,被认为是需要重生成的。这个在前面已经讲过了。

如果命令太长,你可以使用反斜框(‘/’)作为换行符。make对一行上有多少个字符没有限制。规则告诉make两件事,文件的依赖关系和如何成成目标文件。

一般来说,make会以UNIX的标准Shell,也就是/bin/sh来执行命令。


三、在规则中使用通配符

如果我们想定义一系列比较类似的文件,我们很自然地就想起使用通配符。make支持三各通配符:“*”,“?”和“[...]”。这是和Unix的B-Shell是相同的。

波浪号(“~”)字符在文件名中也有比较特殊的用途。如果是“~/test”,这就表示当前用户的$HOME目录下的test目录。而“~hchen/test”则表示用户hchen的宿主目录下的test目录。(这些都是Unix下的小知识了,make也支持)而在Windows或是MS-DOS下,用户没有宿主目录,那么波浪号所指的目录则根据环境变量“HOME”而定。

通配符代替了你一系列的文件,如“*.c”表示所以后缀为c的文件。一个需要我们注意的是,如果我们的文件名中有通配符,如:“*”,那么可以用转义字符“/”,如“/*”来表示真实的“*”字符,而不是任意长度的字符串。

好吧,还是先来看几个例子吧:

clean:
rm -f *.o

上面这个例子我不不多说了,这是操作系统Shell所支持的通配符。这是在命令中的通配符。

print: *.c
lpr -p $?
touch print

上面这个例子说明了通配符也可以在我们的规则中,目标print依赖于所有的[.c]文件。其中的“$?”是一个自动化变量,我会在后面给你讲述。

objects = *.o

上面这个例子,表示了,通符同样可以用在变量中。并不是说[*.o]会展开,不!objects的值就是“*.o”。Makefile中的变量其实就是C/C++中的宏。如果你要让通配符在变量中展开,也就是让objects的值是所有[.o]的文件名的集合,那么,你可以这样:

objects := $(wildcard *.o)

这种用法由关键字“wildcard”指出,关于Makefile的关键字,我们将在后面讨论。


四、文件搜寻

在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当make需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告诉make,让make在自动去找。

Makefile文件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。

VPATH = src:../headers

上面的的定义指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜索的地方)

另一个设置文件搜索路径的方法是使用make的“vpath”关键字(注意,它是全小写的),这不是变量,这是一个make的关键字,这和上面提到的那个VPATH变量很类似,但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种:

1、vpath <pattern> <directories>

为符合模式<pattern>的文件指定搜索目录<directories>。

2、vpath <pattern>

清除符合模式<pattern>的文件的搜索目录。

3、vpath

清除所有已被设置好了的文件搜索目录。

vapth使用方法中的<pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”结尾的文件。<pattern>指定了要搜索的文件集,而<directories>则指定了<pattern>的文件集的搜索的目录。例如:

vpath %.h ../headers

该语句表示,要求make在“../headers”目录下搜索所有以“.h”结尾的文件。(如果某文件在当前目录没有找到的话)

我们可以连续地使用vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的<pattern>,或是被重复了的<pattern>,那么,make会按照vpath语句的先后顺序来执行搜索。如:

vpath %.c foo
vpath % blish
vpath %.c bar

其表示“.c”结尾的文件,先在“foo”目录,然后是“blish”,最后是“bar”目录。

vpath %.c foo:bar
vpath % blish

而上面的语句则表示“.c”结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。


五、伪目标

最早先的一个例子中,我们提到过一个“clean”的目标,这是一个“伪目标”,

clean:
rm *.o temp

正像我们前面例子中的“clean”一样,即然我们生成了许多文件编译文件,我们也应该提供一个清除它们的“目标”以备完整地重编译而用。 (以“make clean”来使用该目标)

因为,我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。

当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。

.PHONY : clean

只要有这个声明,不管是否有“clean”文件,要运行“clean”这个目标,只有“make clean”这样。于是整个过程可以这样写:

.PHONY: clean
clean:
rm *.o temp

伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性:

all : prog1 prog2 prog3
.PHONY : all

prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o

prog2 : prog2.o
cc -o prog2 prog2.o

prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o

我们知道,Makefile中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它三个目标。由于伪目标的特性是,总是被执行的,所以其依赖的那三个目标就总是不如“all”这个目标新。所以,其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。“.PHONY : all”声明了“all”这个目标为“伪目标”。

随便提一句,从上面的例子我们可以看出,目标也可以成为依赖。所以,伪目标同样也可成为依赖。看下面的例子:

.PHONY: cleanall cleanobj cleandiff

cleanall : cleanobj cleandiff
rm program

cleanobj :
rm *.o

cleandiff :
rm *.diff

“make clean”将清除所有要被清除的文件。“cleanobj”和“cleandiff”这两个伪目标有点像“子程序”的意思。我们可以输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清除不同种类文件的目的。

六、多目标

Makefile的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。当然,多个目标的生成规则的执行命令是同一个,这可能会可我们带来麻烦,不过好在我们的可以使用一个自动化变量“$@”(关于自动化变量,将在后面讲述),这个变量表示着目前规则中所有的目标的集合,这样说可能很抽象,还是看一个例子吧。

bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@

上述规则等价于:

bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput

其中,-$(subst output,,$@)中的“$”表示执行一个Makefile的函数,函数名为subst,后面的为参数。关于函数,将在后面讲述。这里的这个函数是截取字符串的意思,“$@”表示目标的集合,就像一个数组,“$@”依次取出目标,并执于命令。


七、静态模式

静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法:

<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
...


targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。

target-parrtern是指明了targets的模式,也就是的目标集模式。

prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。

这样描述这三个东西,可能还是没有说清楚,还是举个例子来说明一下吧。如果我们的<target-parrtern>定义成“%.o”,意思是我们的<target>集合中都是以“.o”结尾的,而如果我们的<prereq-parrterns>定义成“%.c”,意思是对<target-parrtern>所形成的目标集进行二次定义,其计算方法是,取<target-parrtern>模式中的“%”(也就是去掉了[.o]这个结尾),并为其加上[.c]这个结尾,形成的新集合。

所以,我们的“目标模式”或是“依赖模式”中都应该有“%”这个字符,如果你的文件名中有“%”那么你可以使用反斜杠“/”进行转义,来标明真实的“%”字符。

看一个例子:

objects = foo.o bar.o

all: $(objects)

$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@


上面的例子中,指明了我们的目标从$object中获取,“%.o”表明要所有以“.o”结尾的目标,也就是“foo.o bar.o”,也就是变量$object集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foo bar”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“foo.c bar.c”。而命令中的“$<”和“$@”则是自动化变量,“$<”表示所有的依赖目标集(也就是“foo.c bar.c”),“$@”表示目标集(也就是“foo.o bar.o”)。于是,上面的规则展开后等价于下面的规则:

foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o

试想,如果我们的“%.o”有几百个,那种我们只要用这种很简单的“静态模式规则”就可以写完一堆规则,实在是太有效率了。“静态模式规则”的用法很灵活,如果用得好,那会一个很强大的功能。再看一个例子:


files = foo.elc bar.o lose.o

$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<


$(filter %.o,$(files))表示调用Makefile的filter函数,过滤“$filter”集,只要其中模式为“%.o”的内容。其的它内容,我就不用多说了吧。这个例字展示了Makefile中更大的弹性。


八、自动生成依赖性

在Makefile中,我们的依赖关系可能会需要包含一系列的头文件,比如,如果我们的main.c中有一句“#include "defs.h"”,那么我们的依赖关系应该是:

main.o : main.c defs.h

但是,如果是一个比较大型的工程,你必需清楚哪些C文件包含了哪些头文件,并且,你在加入或删除头文件时,也需要小心地修改Makefile,这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情,我们可以使用C/C++编译的一个功能。大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。例如,如果我们执行下面的命令:

cc -M main.c

其输出是:

main.o : main.c defs.h

于是由编译器自动生成的依赖关系,这样一来,你就不必再手动书写若干文件的依赖关系,而由编译器自动生成了。需要提醒一句的是,如果你使用GNU的C/C++编译器,你得用“-MM”参数,不然,“-M”参数会把一些标准库的头文件也包含进来。

gcc -M main.c的输出是:

main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h /
/usr/include/sys/cdefs.h /usr/include/gnu/stubs.h /
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h /
/usr/include/bits/types.h /usr/include/bits/pthreadtypes.h /
/usr/include/bits/sched.h /usr/include/libio.h /
/usr/include/_G_config.h /usr/include/wchar.h /
/usr/include/bits/wchar.h /usr/include/gconv.h /
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h /
/usr/include/bits/stdio_lim.h


gcc -MM main.c的输出则是:

main.o: main.c defs.h

那么,编译器的这个功能如何与我们的Makefile联系在一起呢。因为这样一来,我们的Makefile也要根据这些源文件重新生成,让Makefile自已依赖于源文件?这个功能并不现实,不过我们可以有其它手段来迂回地实现这一功能。GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个“name.c”的文件都生成一个“name.d”的Makefile文件,[.d]文件中就存放对应[.c]文件的依赖关系。

于是,我们可以写出[.c]文件和[.d]文件的依赖关系,并让make自动更新或自成[.d]文件,并把其包含在我们的主Makefile中,这样,我们就可以自动化地生成每个文件的依赖关系了。

这里,我们给出了一个模式规则来产生[.d]文件:

%.d: %.c
@set -e; rm -f $@; /
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; /
sed 's,/($*/)/.o[ :]*,/1.o $@ : ,g' < $@.$$$$ > $@; /
rm -f $@.$$$$


这个规则的意思是,所有的[.d]文件依赖于[.c]文件,“rm -f $@”的意思是删除所有的目标,也就是[.d]文件,第二行的意思是,为每个依赖文件“$<”,也就是[.c]文件生成依赖文件,“$@”表示模式“%.d”文件,如果有一个C文件是name.c,那么“%”就是“name”,“$$$$”意为一个随机编号,第二行生成的文件有可能是“name.d.12345”,第三行使用sed命令做了一个替换,关于sed命令的用法请参看相关的使用文档。第四行就是删除临时文件。

总而言之,这个模式要做的事就是在编译器生成的依赖关系中加入[.d]文件的依赖,即把依赖关系:

main.o : main.c defs.h

转成:

main.o main.d : main.c defs.h

于是,我们的[.d]文件也会自动更新了,并会自动生成了,当然,你还可以在这个[.d]文件中加入的不只是依赖关系,包括生成的命令也可一并加入,让每个[.d]文件都包含一个完赖的规则。一旦我们完成这个工作,接下来,我们就要把这些自动生成的规则放进我们的主Makefile中。我们可以使用Makefile的“include”命令,来引入别的Makefile文件(前面讲过),例如:

sources = foo.c bar.c

include $(sources:.c=.d)

上述语句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一个替换,把变量$(sources)所有[.c]的字串都替换成[.d],关于这个“替换”的内容,在后面我会有更为详细的讲述。当然,你得注意次序,因为include是按次来载入文件,最先载入的[.d]文件中的目标会成为默认目标。



书写命令
————

每条规则中的命令和操作系统Shell的命令行是一致的。make会一按顺序一条一条的执行命令,每条命令的开头必须以[Tab]键开头,除非,命令是紧跟在依赖规则后面的分号后的。在命令行之间中的空格或是空行会被忽略,但是如果该空格或空行是以Tab键开头的,那么make会认为其是一个空命令。

我们在UNIX下可能会使用不同的Shell,但是make的命令默认是被“/bin/sh”——UNIX的标准Shell解释执行的。除非你特别指定一个其它的Shell。Makefile中,“#”是注释符,很像C/C++中的“//”,其后的本行字符都被注释。

一、显示命令

通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用“@”字符在命令行前,那么,这个命令将不被make显示出来,最具代表性的例子是,我们用这个功能来像屏幕显示一些信息。如:

@echo 正在编译XXX模块......

当make执行时,会输出“正在编译XXX模块......”字串,但不会输出命令,如果没有“@”,那么,make将输出:

echo 正在编译XXX模块......
正在编译XXX模块......

如果make执行时,带入make参数“-n”或“--just-print”,那么其只是显示命令,但不会执行命令,这个功能很有利于我们调试我们的Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺序的。

而make参数“-s”或“--slient”则是全面禁止命令的显示。



二、命令执行

当依赖目标新于目标时,也就是当规则的目标需要被更新时,make会一条一条的执行其后的命令。需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一条命令是cd命令,你希望第二条命令得在cd之后的基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。如:

示例一:
exec:
cd /home/hchen
pwd

示例二:
exec:
cd /home/hchen; pwd

当我们执行“make exec”时,第一个例子中的cd没有作用,pwd会打印出当前的Makefile目录,而第二个例子中,cd就起作用了,pwd会打印出“/home/hchen”。

make一般是使用环境变量SHELL中所定义的系统Shell来执行命令,默认情况下使用UNIX的标准Shell——/bin/sh来执行命令。但在MS-DOS下有点特殊,因为MS-DOS下没有SHELL环境变量,当然你也可以指定。如果你指定了UNIX风格的目录形式,首先,make会在SHELL所指定的路径中找寻命令解释器,如果找不到,其会在当前盘符中的当前目录中寻找,如果再找不到,其会在PATH环境变量中所定义的所有路径中寻找。MS-DOS中,如果你定义的命令解释器没有找到,其会给你的命令解释器加上诸如“.exe”、“.com”、“.bat”、“.sh”等后缀。

作者: compare2000    时间: 2014-06-24 16:55
三、命令出错

每当命令运行完后,make会检测每个命令的返回码,如果命令返回成功,那么make会执行下一条命令,当规则中所有的命令成功返回后,这个规则就算是成功完成了。如果一个规则中的某个命令出错了(命令退出码非零),那么make就会终止执行当前规则,这将有可能终止所有规则的执行。

有些时候,命令的出错并不表示就是错误的。例如mkdir命令,我们一定需要建立一个目录,如果目录不存在,那么mkdir就成功执行,万事大吉,如果目录存在,那么就出错了。我们之所以使用mkdir的意思就是一定要有这样的一个目录,于是我们就不希望mkdir出错而终止规则的运行。

为了做到这一点,忽略命令的出错,我们可以在Makefile的命令行前加一个减号“-”(在Tab键之后),标记为不管命令出不出错都认为是成功的。如:

clean:
-rm -f *.o

还有一个全局的办法是,给make加上“-i”或是“--ignore-errors”参数,那么,Makefile中所有命令都会忽略错误。而如果一个规则是以“.IGNORE”作为目标的,那么这个规则中的所有命令将会忽略错误。这些是不同级别的防止命令出错的方法,你可以根据你的不同喜欢设置。

还有一个要提一下的make的参数的是“-k”或是“--keep-going”,这个参数的意思是,如果某规则中的命令出错了,那么就终目该规则的执行,但继续执行其它规则。



四、嵌套执行make

在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的Makefile,这有利于让我们的Makefile变得更加地简洁,而不至于把所有的东西全部写在一个Makefile中,这样会很难维护我们的Makefile,这个技术对于我们模块编译和分段编译有着非常大的好处。

例如,我们有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写:

subsystem:
cd subdir && $(MAKE)

其等价于:

subsystem:
$(MAKE) -C subdir

定义$(MAKE)宏变量的意思是,也许我们的make需要一些参数,所以定义成一个变量比较利于维护。这两个例子的意思都是先进入“subdir”目录,然后执行make命令。

我们把这个Makefile叫做“总控Makefile”,总控Makefile的变量可以传递到下级的Makefile中(如果你显示的声明),但是不会覆盖下层的Makefile中所定义的变量,除非指定了“-e”参数。

如果你要传递变量到下级Makefile中,那么你可以使用这样的声明:

export <variable ...>

如果你不想让某些变量传递到下级Makefile中,那么你可以这样声明:

unexport <variable ...>

如:

示例一:

export variable = value

其等价于:

variable = value
export variable

其等价于:

export variable := value

其等价于:

variable := value
export variable

示例二:

export variable += value

其等价于:

variable += value
export variable

如果你要传递所有的变量,那么,只要一个export就行了。后面什么也不用跟,表示传递所有的变量。

需要注意的是,有两个变量,一个是SHELL,一个是MAKEFLAGS,这两个变量不管你是否export,其总是要传递到下层Makefile中,特别是MAKEFILES变量,其中包含了make的参数信息,如果我们执行“总控Makefile”时有make参数或是在上层Makefile中定义了这个变量,那么MAKEFILES变量将会是这些参数,并会传递到下层Makefile中,这是一个系统级的环境变量。

但是make命令中的有几个参数并不往下传递,它们是“-C”,“-f”,“-h”“-o”和“-W”(有关Makefile参数的细节将在后面说明),如果你不想往下层传递参数,那么,你可以这样来:

subsystem:
cd subdir && $(MAKE) MAKEFLAGS=

如果你定义了环境变量MAKEFLAGS,那么你得确信其中的选项是大家都会用到的,如果其中有“-t”,“-n”,和“-q”参数,那么将会有让你意想不到的结果,或许会让你异常地恐慌。

还有一个在“嵌套执行”中比较有用的参数,“-w”或是“--print-directory”会在make的过程中输出一些信息,让你看到目前的工作目录。比如,如果我们的下级make目录是“/home/hchen/gnu/make”,如果我们使用“make -w”来执行,那么当进入该目录时,我们会看到:

make: Entering directory `/home/hchen/gnu/make'.

而在完成下层make后离开目录时,我们会看到:

make: Leaving directory `/home/hchen/gnu/make'

当你使用“-C”参数来指定make下层Makefile时,“-w”会被自动打开的。如果参数中有“-s”(“--slient”)或是“--no-print-directory”,那么,“-w”总是失效的。



五、定义命令包

如果Makefile中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以“define”开始,以“endef”结束,如:

define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef

这里,“run-yacc”是这个命令包的名字,其不要和Makefile中的变量重名。在“define”和“endef”中的两行就是命令序列。这个命令包中的第一个命令是运行Yacc程序,因为Yacc程序总是生成“y.tab.c”的文件,所以第二行的命令就是把这个文件改改名字。还是把这个命令包放到一个示例中来看看吧。

foo.c : foo.y
$(run-yacc)

我们可以看见,要使用这个命令包,我们就好像使用变量一样。在这个命令包的使用中,命令包“run-yacc”中的“$^”就是“foo.y”,“$@”就是“foo.c”(有关这种以“$”开头的特殊变量,我们会在后面介绍),make在执行命令包时,命令包中的每个命令会被依次独立执行。

使用变量
————

在Makefile中的定义的变量,就像是C/C++语言中的宏一样,他代表了一个文本字串,在Makefile中执行的时候其会自动原模原样地展开在所使用的地方。其与C/C++所不同的是,你可以在Makefile中改变其值。在Makefile中,变量可以使用在“目标”,“依赖目标”,“命令”或是Makefile的其它部分中。

变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有“:”、“#”、“=”或是空字符(空格、回车等)。变量是大小写敏感的,“foo”、“Foo”和“FOO”是三个不同的变量名。传统的Makefile的变量名是全大写的命名方式,但我推荐使用大小写搭配的变量名,如:MakeFlags。这样可以避免和系统的变量冲突,而发生意外的事情。

有一些变量是很奇怪字串,如“$<”、“$@”等,这些是自动化变量,我会在后面介绍。

一、变量的基础

变量在声明时需要给予初值,而在使用时,需要给在变量名前加上“$”符号,但最好用小括号“()”或是大括号“{}”把变量给包括起来。如果你要使用真实的“$”字符,那么你需要用“$$”来表示。

变量可以使用在许多地方,如规则中的“目标”、“依赖”、“命令”以及新的变量中。先看一个例子:

objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)

$(objects) : defs.h

变量会在使用它的地方精确地展开,就像C/C++中的宏一样,例如:

foo = c
prog.o : prog.$(foo)
$(foo)$(foo) -$(foo) prog.$(foo)

展开后得到:

prog.o : prog.c
cc -c prog.c

当然,千万不要在你的Makefile中这样干,这里只是举个例子来表明Makefile中的变量在使用处展开的真实样子。可见其就是一个“替代”的原理。

另外,给变量加上括号完全是为了更加安全地使用这个变量,在上面的例子中,如果你不想给变量加上括号,那也可以,但我还是强烈建议你给变量加上括号。


二、变量中的变量

在定义变量的值时,我们可以使用其它变量来构造变量的值,在Makefile中有两种方式来在用变量定义变量的值。

先看第一种方式,也就是简单的使用“=”号,在“=”左侧是变量,右侧是变量的值,右侧变量的值可以定义在文件的任何一处,也就是说,右侧中的变量不一定非要是已定义好的值,其也可以使用后面定义的值。如:

foo = $(bar)
bar = $(ugh)
ugh = Huh?

all:
echo $(foo)

我们执行“make all”将会打出变量$(foo)的值是“Huh?”( $(foo)的值是$(bar),$(bar)的值是$(ugh),$(ugh)的值是“Huh?”)可见,变量是可以使用后面的变量来定义的。

这个功能有好的地方,也有不好的地方,好的地方是,我们可以把变量的真实值推到后面来定义,如:

CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar

当“CFLAGS”在命令中被展开时,会是“-Ifoo -Ibar -O”。但这种形式也有不好的地方,那就是递归定义,如:

CFLAGS = $(CFLAGS) -O

或:

A = $(B)
B = $(A)

这会让make陷入无限的变量展开过程中去,当然,我们的make是有能力检测这样的定义,并会报错。还有就是如果在变量中使用函数,那么,这种方式会让我们的make运行时非常慢,更糟糕的是,他会使用得两个make的函数“wildcard”和“shell”发生不可预知的错误。因为你不会知道这两个函数会被调用多少次。

为了避免上面的这种方法,我们可以使用make中的另一种用变量来定义变量的方法。这种方法使用的是“:=”操作符,如:

x := foo
y := $(x) bar
x := later

其等价于:

y := foo bar
x := later

值得一提的是,这种方法,前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。如果是这样:

y := $(x) bar
x := foo

那么,y的值是“bar”,而不是“foo bar”。

上面都是一些比较简单的变量使用了,让我们来看一个复杂的例子,其中包括了make的函数、条件表达式和一个系统变量“MAKELEVEL”的使用:

ifeq (0,${MAKELEVEL})
cur-dir := $(shell pwd)
whoami := $(shell whoami)
host-type := $(shell arch)
MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif

关于条件表达式和函数,我们在后面再说,对于系统变量“MAKELEVEL”,其意思是,如果我们的make有一个嵌套执行的动作(参见前面的“嵌套使用make”),那么,这个变量会记录了我们的当前Makefile的调用层数。

下面再介绍两个定义变量时我们需要知道的,请先看一个例子,如果我们要定义一个变量,其值是一个空格,那么我们可以这样来:

nullstring :=
space := $(nullstring) # end of the line

nullstring是一个Empty变量,其中什么也没有,而我们的space的值是一个空格。因为在操作符的右边是很难描述一个空格的,这里采用的技术很管用,先用一个Empty变量来标明变量的值开始了,而后面采用“#”注释符来表示变量定义的终止,这样,我们可以定义出其值是一个空格的变量。请注意这里关于“#”的使用,注释符“#”的这种特性值得我们注意,如果我们这样定义一个变量:

dir := /foo/bar # directory to put the frobs in

dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我们这样使用这样变量来指定别的目录——“$(dir)/file”那么就完蛋了。

还有一个比较有用的操作符是“?=”,先看示例:

FOO ?= bar

其含义是,如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将什么也不做,其等价于:

ifeq ($(origin FOO), undefined)
FOO = bar
endif


三、变量高级用法

这里介绍两种变量的高级使用方法,第一种是变量值的替换。

我们可以替换变量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。

还是看一个示例吧:

foo := a.o b.o c.o
bar := $(foo:.o=.c)

这个示例中,我们先定义了一个“$(foo)”变量,而第二行的意思是把“$(foo)”中所有以“.o”字串“结尾”全部替换成“.c”,所以我们的“$(bar)”的值就是“a.c b.c c.c”。

另外一种变量替换的技术是以“静态模式”(参见前面章节)定义的,如:

foo := a.o b.o c.o
bar := $(foo:%.o=%.c)

这依赖于被替换字串中的有相同的模式,模式中必须包含一个“%”字符,这个例子同样让$(bar)变量的值为“a.c b.c c.c”。

第二种高级用法是——“把变量的值再当成变量”。先看一个例子:

x = y
y = z
a := $($(x))

在这个例子中,$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”。(注意,是“x=y”,而不是“x=$(y)”)

我们还可以使用更多的层次:

x = y
y = z
z = u
a := $($($(x)))

这里的$(a)的值是“u”,相关的推导留给读者自己去做吧。

让我们再复杂一点,使用上“在变量定义中使用变量”的第一个方式,来看一个例子:

x = $(y)
y = z
z = Hello
a := $($(x))

这里的$($(x))被替换成了$($(y)),因为$(y)值是“z”,所以,最终结果是:a:=$(z),也就是“Hello”。

再复杂一点,我们再加上函数:

x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))

这个例子中,“$($($(z)))”扩展为“$($(y))”,而其再次被扩展为“$($(subst 1,2,$(x)))”。$(x)的值是“variable1”,subst函数把“variable1”中的所有“1”字串替换成“2”字串,于是,“variable1”变成“variable2”,再取其值,所以,最终,$(a)的值就是$(variable2)的值——“Hello”。(喔,好不容易)

在这种方式中,或要可以使用多个变量来组成一个变量的名字,然后再取其值:

first_second = Hello
a = first
b = second
all = $($a_$b)

这里的“$a_$b”组成了“first_second”,于是,$(all)的值就是“Hello”。

再来看看结合第一种技术的例子:

a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o

sources := $($(a1)_objects:.o=.c)

这个例子中,如果$(a1)的值是“a”的话,那么,$(sources)的值就是“a.c b.c c.c”;如果$(a1)的值是“1”,那么$(sources)的值是“1.c 2.c 3.c”。

再来看一个这种技术和“函数”与“条件语句”一同使用的例子:

ifdef do_sort
func := sort
else
func := strip
endif

bar := a d b g q c

foo := $($(func) $(bar))

这个示例中,如果定义了“do_sort”,那么:foo := $(sort a d b g q c),于是$(foo)的值就是“a b c d g q”,而如果没有定义“do_sort”,那么:foo := $(sort a d b g q c),调用的就是strip函数。

当然,“把变量的值再当成变量”这种技术,同样可以用在操作符的左边:

dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print
lpr $($(dir)_sources)
endef

这个例子中定义了三个变量:“dir”,“foo_sources”和“foo_print”。


四、追加变量值

我们可以使用“+=”操作符给变量追加值,如:

objects = main.o foo.o bar.o utils.o
objects += another.o

于是,我们的$(objects)值变成:“main.o foo.o bar.o utils.o another.o”(another.o被追加进去了)

使用“+=”操作符,可以模拟为下面的这种例子:

objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o

所不同的是,用“+=”更为简洁。

如果变量之前没有定义过,那么,“+=”会自动变成“=”,如果前面有变量定义,那么“+=”会继承于前次操作的赋值符。如果前一次的是“:=”,那么“+=”会以“:=”作为其赋值符,如:

variable := value
variable += more

等价于:

variable := value
variable := $(variable) more

但如果是这种情况:

variable = value
variable += more

由于前次的赋值符是“=”,所以“+=”也会以“=”来做为赋值,那么岂不会发生变量的递补归定义,这是很不好的,所以make会自动为我们解决这个问题,我们不必担心这个问题。


五、override 指示符

如果有变量是通常make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那么,你可以使用“override”指示符。其语法是:

override <variable> = <value>

override <variable> := <value>

当然,你还可以追加:

override <variable> += <more text>

对于多行的变量定义,我们用define指示符,在define指示符前,也同样可以使用ovveride指示符,如:

override define foo
bar
endef

六、多行变量

还有一种设置变量值的方法是使用define关键字。使用define关键字设置变量的值可以有换行,这有利于定义一系列的命令(前面我们讲过“命令包”的技术就是利用这个关键字)。

define指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以endef关键字结束。其工作方式和“=”操作符一样。变量的值可以包含函数、命令、文字,或是其它变量。因为命令需要以[Tab]键开头,所以如果你用define定义的命令变量中没有以[Tab]键开头,那么make就不会把其认为是命令。

下面的这个示例展示了define的用法:

define two-lines
echo foo
echo $(bar)
endef
作者: compare2000    时间: 2014-06-24 16:55
七、环境变量

make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中,但是如果Makefile中已定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖。(如果make指定了“-e”参数,那么,系统环境变量将覆盖Makefile中定义的变量)

因此,如果我们在环境变量中设置了“CFLAGS”环境变量,那么我们就可以在所有的Makefile中使用这个变量了。这对于我们使用统一的编译参数有比较大的好处。如果Makefile中定义了CFLAGS,那么则会使用Makefile中的这个变量,如果没有定义则使用系统环境变量的值,一个共性和个性的统一,很像“全局变量”和“局部变量”的特性。

当make嵌套调用时(参见前面的“嵌套调用”章节),上层Makefile中定义的变量会以系统环境变量的方式传递到下层的Makefile中。当然,默认情况下,只有通过命令行设置的变量会被传递。而定义在文件中的变量,如果要向下层Makefile传递,则需要使用exprot关键字来声明。(参见前面章节)

当然,我并不推荐把许多的变量都定义在系统环境中,这样,在我们执行不用的Makefile时,拥有的是同一套系统变量,这可能会带来更多的麻烦。


八、目标变量

前面我们所讲的在Makefile中定义的变量都是“全局变量”,在整个文件,我们都可以访问这些变量。当然,“自动化变量”除外,如“$<”等这种类量的自动化变量就属于“规则型变量”,这种变量的值依赖于规则的目标和依赖目标的定义。

当然,我样同样可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。

其语法是:

<target ...> : <variable-assignment>

<target ...> : overide <variable-assignment>

<variable-assignment>可以是前面讲过的各种赋值表达式,如“=”、“:=”、“+=”或是“?=”。第二个语法是针对于make命令行带入的变量,或是系统环境变量。

这个特性非常的有用,当我们设置了这样一个变量,这个变量会作用到由这个目标所引发的所有的规则中去。如:

prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.o

prog.o : prog.c
$(CC) $(CFLAGS) prog.c

foo.o : foo.c
$(CC) $(CFLAGS) foo.c

bar.o : bar.c
$(CC) $(CFLAGS) bar.c

在这个示例中,不管全局的$(CFLAGS)的值是什么,在prog目标,以及其所引发的所有规则中(prog.o foo.o bar.o的规则),$(CFLAGS)的值都是“-g”


九、模式变量

在GNU的make中,还支持模式变量(Pattern-specific Variable),通过上面的目标变量中,我们知道,变量可以定义在某个目标上。模式变量的好处就是,我们可以给定一种“模式”,可以把变量定义在符合这种模式的所有目标上。

我们知道,make的“模式”一般是至少含有一个“%”的,所以,我们可以以如下方式给所有以[.o]结尾的目标定义目标变量:

%.o : CFLAGS = -O

同样,模式变量的语法和“目标变量”一样:

<pattern ...> : <variable-assignment>

<pattern ...> : override <variable-assignment>

override同样是针对于系统环境传入的变量,或是make命令行指定的变量。

使用条件判断
——————

使用条件判断,可以让make根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值,或是比较变量和常量的值。

一、示例

下面的例子,判断$(CC)变量是否“gcc”,如果是的话,则使用GNU函数编译目标。

libs_for_gcc = -lgnu
normal_libs =

foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif

可见,在上面示例的这个规则中,目标“foo”可以根据变量“$(CC)”值来选取不同的函数库来编译程序。

我们可以从上面的示例中看到三个关键字:ifeq、else和endif。ifeq的意思表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括号括起。else表示条件表达式为假的情况。endif表示一个条件语句的结束,任何一个条件表达式都应该以endif结束。

当我们的变量$(CC)值是“gcc”时,目标foo的规则是:

foo: $(objects)
$(CC) -o foo $(objects) $(libs_for_gcc)

而当我们的变量$(CC)值不是“gcc”时(比如“cc”),目标foo的规则是:

foo: $(objects)
$(CC) -o foo $(objects) $(normal_libs)

当然,我们还可以把上面的那个例子写得更简洁一些:

libs_for_gcc = -lgnu
normal_libs =

ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif

foo: $(objects)
$(CC) -o foo $(objects) $(libs)


二、语法

条件表达式的语法为:

<conditional-directive>
<text-if-true>
endif

以及:

<conditional-directive>
<text-if-true>
else
<text-if-false>
endif

其中<conditional-directive>表示条件关键字,如“ifeq”。这个关键字有四个。

第一个是我们前面所见过的“ifeq”

ifeq (<arg1>, <arg2> )
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"

比较参数“arg1”和“arg2”的值是否相同。当然,参数中我们还可以使用make的函数。如:

ifeq ($(strip $(foo)),)
<text-if-empty>
endif

这个示例中使用了“strip”函数,如果这个函数的返回值是空(Empty),那么<text-if-empty>就生效。

第二个条件关键字是“ifneq”。语法是:

ifneq (<arg1>, <arg2> )
ifneq '<arg1>' '<arg2>'
ifneq "<arg1>" "<arg2>"
ifneq "<arg1>" '<arg2>'
ifneq '<arg1>' "<arg2>"

其比较参数“arg1”和“arg2”的值是否相同,如果不同,则为真。和“ifeq”类似。

第三个条件关键字是“ifdef”。语法是:

ifdef <variable-name>

如果变量<variable-name>的值非空,那到表达式为真。否则,表达式为假。当然,<variable-name>同样可以是一个函数的返回值。注意,ifdef只是测试一个变量是否有值,其并不会把变量扩展到当前位置。还是来看两个例子:

示例一:
bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif

示例二:
foo =
ifdef foo
frobozz = yes
else
frobozz = no
endif

第一个例子中,“$(frobozz)”值是“yes”,第二个则是“no”。

第四个条件关键字是“ifndef”。其语法是:

ifndef <variable-name>

这个我就不多说了,和“ifdef”是相反的意思。

在<conditional-directive>这一行上,多余的空格是被允许的,但是不能以[Tab]键做为开始(不然就被认为是命令)。而注释符“#”同样也是安全的。“else”和“endif”也一样,只要不是以[Tab]键开始就行了。

特别注意的是,make是在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句,所以,你最好不要把自动化变量(如“$@”等)放入条件表达式中,因为自动化变量是在运行时才有的。

而且,为了避免混乱,make不允许把整个条件语句分成两部分放在不同的文件中。



使用函数
————

在Makefile中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make所支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。


一、函数的调用语法

函数调用,很像变量的使用,也是以“$”来标识的,其语法如下:

$(<function> <arguments> )

或是

${<function> <arguments>}

这里,<function>就是函数名,make支持的函数不多。<arguments>是函数的参数,参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔。函数调用以“$”开头,以圆括号或花括号把函数名和参数括起。感觉很像一个变量,是不是?函数中的参数可以使用变量,为了风格的统一,函数和变量的括号最好一样,如使用“$(subst a,b,$(x))”这样的形式,而不是“$(subst a,b,${x})”的形式。因为统一会更清楚,也会减少一些不必要的麻烦。

还是来看一个示例:

comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))

在这个示例中,$(comma)的值是一个逗号。$(space)使用了$(empty)定义了一个空格,$(foo)的值是“a b c”,$(bar)的定义用,调用了函数“subst”,这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作作用的字串。这个函数也就是把$(foo)中的空格替换成逗号,所以$(bar)的值是“a,b,c”。


二、字符串处理函数

$(subst <from>,<to>,<text> )

名称:字符串替换函数——subst。
功能:把字串<text>中的<from>字符串替换成<to>。
返回:函数返回被替换过后的字符串。

示例:

$(subst ee,EE,feet on the street),

把“feet on the street”中的“ee”替换成“EE”,返回结果是“fEEt on the strEEt”。


$(patsubst <pattern>,<replacement>,<text> )

名称:模式字符串替换函数——patsubst。
功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。这里,<pattern>可以包括通配符“%”,表示任意长度的字串。如果<replacement>中也包含“%”,那么,<replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串。(可以用“/”来转义,以“/%”来表示真实含义的“%”字符)
返回:函数返回被替换过后的字符串。

示例:

$(patsubst %.c,%.o,x.c.c bar.c)

把字串“x.c.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.c.o bar.o”

备注:

这和我们前面“变量章节”说过的相关知识有点相似。如:

“$(var:<pattern>=<replacement> )”
相当于
“$(patsubst <pattern>,<replacement>,$(var))”,

而“$(var: <suffix>=<replacement> )”
则相当于
“$(patsubst %<suffix>,%<replacement>,$(var))”。

例如有:objects = foo.o bar.o baz.o,
那么,“$(objects:.o=.c)”和“$(patsubst %.o,%.c,$(objects))”是一样的。

$(strip <string> )

名称:去空格函数——strip。
功能:去掉<string>字串中开头和结尾的空字符。
返回:返回被去掉空格的字符串值。
示例:

$(strip a b c )

把字串“a b c ”去到开头和结尾的空格,结果是“a b c”。

$(findstring <find>,<in> )

名称:查找字符串函数——findstring。
功能:在字串<in>中查找<find>字串。
返回:如果找到,那么返回<find>,否则返回空字符串。
示例:

$(findstring a,a b c)
$(findstring a,b c)

第一个函数返回“a”字符串,第二个返回“”字符串(空字符串)

$(filter <pattern...>,<text> )

名称:过滤函数——filter。
功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可以有多个模式。
返回:返回符合模式<pattern>的字串。
示例:

sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo

$(filter %.c %.s,$(sources))返回的值是“foo.c bar.c baz.s”。

$(filter-out <pattern...>,<text> )

名称:反过滤函数——filter-out。
功能:以<pattern>模式过滤<text>字符串中的单词,去除符合模式<pattern>的单词。可以有多个模式。
返回:返回不符合模式<pattern>的字串。
示例:

objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o

$(filter-out $(mains),$(objects)) 返回值是“foo.o bar.o”。

$(sort <list> )

名称:排序函数——sort。
功能:给字符串<list>中的单词排序(升序)。
返回:返回排序后的字符串。
示例:$(sort foo bar lose)返回“bar foo lose” 。
备注:sort函数会去掉<list>中相同的单词。

$(word <n>,<text> )

名称:取单词函数——word。
功能:取字符串<text>中第<n>个单词。(从一开始)
返回:返回字符串<text>中第<n>个单词。如果<n>比<text>中的单词数要大,那么返回空字符串。
示例:$(word 2, foo bar baz)返回值是“bar”。

$(wordlist <s>,<e>,<text> )

名称:取单词串函数——wordlist。
功能:从字符串<text>中取从<s>开始到<e>的单词串。<s>和<e>是一个数字。
返回:返回字符串<text>中从<s>到<e>的单词字串。如果<s>比<text>中的单词数要大,那么返回空字符串。如果<e>大于<text>的单词数,那么返回从<s>开始,到<text>结束的单词串。
示例: $(wordlist 2, 3, foo bar baz)返回值是“bar baz”。

$(words <text> )

名称:单词个数统计函数——words。
功能:统计<text>中字符串中的单词个数。
返回:返回<text>中的单词数。
示例:$(words, foo bar baz)返回值是“3”。
备注:如果我们要取<text>中最后的一个单词,我们可以这样:$(word $(words <text> ),<text> )。

$(firstword <text> )

名称:首单词函数——firstword。
功能:取字符串<text>中的第一个单词。
返回:返回字符串<text>的第一个单词。
示例:$(firstword foo bar)返回值是“foo”。
备注:这个函数可以用word函数来实现:$(word 1,<text> )。

以上,是所有的字符串操作函数,如果搭配混合使用,可以完成比较复杂的功能。这里,举一个现实中应用的例子。我们知道,make使用“VPATH”变量来指定“依赖文件”的搜索路径。于是,我们可以利用这个搜索路径来指定编译器对头文件的搜索路径参数CFLAGS,如:

override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))

如果我们的“$(VPATH)”值是“src:../headers”,那么“$(patsubst %,-I%,$(subst :, ,$(VPATH)))”将返回“-Isrc -I../headers”,这正是cc或gcc搜索头文件路径的参数。
作者: compare2000    时间: 2014-06-24 16:56
三、文件名操作函数

下面我们要介绍的函数主要是处理文件名的。每个函数的参数字符串都会被当做一个或是一系列的文件名来对待。

$(dir <names...> )

名称:取目录函数——dir。
功能:从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部分。如果没有反斜杠,那么返回“./”。
返回:返回文件名序列<names>的目录部分。
示例: $(dir src/foo.c hacks)返回值是“src/ ./”。

$(notdir <names...> )

名称:取文件函数——notdir。
功能:从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后的部分。
返回:返回文件名序列<names>的非目录部分。
示例: $(notdir src/foo.c hacks)返回值是“foo.c hacks”。

$(suffix <names...> )

名称:取后缀函数——suffix。
功能:从文件名序列<names>中取出各个文件名的后缀。
返回:返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字串。
示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是“.c .c”。

$(basename <names...> )

名称:取前缀函数——basename。
功能:从文件名序列<names>中取出各个文件名的前缀部分。
返回:返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字串。
示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是“src/foo src-1.0/bar hacks”。

$(addsuffix <suffix>,<names...> )

名称:加后缀函数——addsuffix。
功能:把后缀<suffix>加到<names>中的每个单词后面。
返回:返回加过后缀的文件名序列。
示例:$(addsuffix .c,foo bar)返回值是“foo.c bar.c”。

$(addprefix <prefix>,<names...> )

名称:加前缀函数——addprefix。
功能:把前缀<prefix>加到<names>中的每个单词后面。
返回:返回加过前缀的文件名序列。
示例:$(addprefix src/,foo bar)返回值是“src/foo src/bar”。

$(join <list1>,<list2> )

名称:连接函数——join。
功能:把<list2>中的单词对应地加到<list1>的单词后面。如果<list1>的单词个数要比<list2>的多,那么,<list1>中的多出来的单词将保持原样。如果<list2>的单词个数要比<list1>多,那么,<list2>多出来的单词将被复制到<list2>中。
返回:返回连接过后的字符串。
示例:$(join aaa bbb , 111 222 333)返回值是“aaa111 bbb222 333”。



四、foreach 函数


foreach函数和别的函数非常的不一样。因为这个函数是用来做循环用的,Makefile中的foreach函数几乎是仿照于Unix标准Shell(/bin/sh)中的for语句,或是C-Shell(/bin/csh)中的foreach语句而构建的。它的语法是:



$(foreach <var>,<list>,<text> )



这个函数的意思是,把参数<list>中的单词逐一取出放到参数<var>所指定的变量中,然后再执行<text>所包含的表达式。每一次<text>会返回一个字符串,循环过程中,<text>的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text>所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。



所以,<var>最好是一个变量名,<list>可以是一个表达式,而<text>中一般会使用<var>这个参数来依次枚举<list>中的单词。举个例子:



names := a b c d

files := $(foreach n,$(names),$(n).o)



上面的例子中,$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以,$(files)的值是“a.o b.o c.o d.o”。



注意,foreach中的<var>参数是一个临时的局部变量,foreach函数执行完后,参数<var>的变量将不在作用,其作用域只在foreach函数当中。





五、if 函数


if函数很像GNU的make所支持的条件语句——ifeq(参见前面所述的章节),if函数的语法是:



$(if <condition>,<then-part> )



或是



$(if <condition>,<then-part>,<else-part> )



可见,if函数可以包含“else”部分,或是不含。即if函数的参数可以是两个,也可以是三个。<condition>参数是if的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真,于是,<then-part>会被计算,否则<else-part>会被计算。



而if函数的返回值是,如果<condition>为真(非空字符串),那个<then-part>会是整个函数的返回值,如果<condition>为假(空字符串),那么<else-part>会是整个函数的返回值,此时如果<else-part>没有被定义,那么,整个函数返回空字串。



所以,<then-part>和<else-part>只会有一个被计算。





六、call函数


call函数是唯一一个可以用来创建新的参数化的函数。你可以写一个非常复杂的表达式,这个表达式中,你可以定义许多参数,然后你可以用call函数来向这个表达式传递参数。其语法是:



$(call <expression>,<parm1>,<parm2>,<parm3>...)



当make执行这个函数时,<expression>参数中的变量,如$(1),$(2),$(3)等,会被参数<parm1>,<parm2>,<parm3>依次取代。而<expression>的返回值就是call函数的返回值。例如:

reverse = $(1) $(2)

foo = $(call reverse,a,b)



那么,foo的值就是“a b”。当然,参数的次序是可以自定义的,不一定是顺序的,如:



reverse = $(2) $(1)

foo = $(call reverse,a,b)



此时的foo的值就是“b a”。





七、origin函数
origin函数不像其它的函数,他并不操作变量的值,他只是告诉你你的这个变量是哪里来的?其语法是:



$(origin <variable> )



注意,<variable>是变量的名字,不应该是引用。所以你最好不要在<variable>中使用“$”字符。Origin函数会以其返回值来告诉你这个变量的“出生情况”,下面,是origin函数的返回值:



“undefined”

如果<variable>从来没有定义过,origin函数返回这个值“undefined”。



“default”

如果<variable>是一个默认的定义,比如“CC”这个变量,这种变量我们将在后面讲述。



“environment”

如果<variable>是一个环境变量,并且当Makefile被执行时,“-e”参数没有被打开。



“file”

如果<variable>这个变量被定义在Makefile中。



“command line”

如果<variable>这个变量是被命令行定义的。



“override”

如果<variable>是被override指示符重新定义的。



“automatic”

如果<variable>是一个命令运行中的自动化变量。关于自动化变量将在后面讲述。



这些信息对于我们编写Makefile是非常有用的,例如,假设我们有一个Makefile其包了一个定义文件Make.def,在Make.def中定义了一个变量“bletch”,而我们的环境中也有一个环境变量“bletch”,此时,我们想判断一下,如果变量来源于环境,那么我们就把之重定义了,如果来源于Make.def或是命令行等非环境的,那么我们就不重新定义它。于是,在我们的Makefile中,我们可以这样写:



ifdef bletch

ifeq "$(origin bletch)" "environment"

bletch = barf, gag, etc.

endif

endif



当然,你也许会说,使用override关键字不就可以重新定义环境中的变量了吗?为什么需要使用这样的步骤?是的,我们用override是可以达到这样的效果,可是override过于粗暴,它同时会把从命令行定义的变量也覆盖了,而我们只想重新定义环境传来的,而不想重新定义命令行传来的。





八、shell函数


shell函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell的命令。它和反引号“`”是相同的功能。这就是说,shell函数把执行操作系统命令后的输出作为函数返回。于是,我们可以用操作系统命令以及字符串处理命令awk,sed等等命令来生成一个变量,如:



contents := $(shell cat foo)



files := $(shell echo *.c)



注意,这个函数会新生成一个Shell程序来执行命令,所以你要注意其运行性能,如果你的Makefile中有一些比较复杂的规则,并大量使用了这个函数,那么对于你的系统性能是有害的。特别是Makefile的隐晦的规则可能会让你的shell函数执行的次数比你想像的多得多。





九、控制make的函数


make提供了一些函数来控制make的运行。通常,你需要检测一些运行Makefile时的运行时信息,并且根据这些信息来决定,你是让make继续执行,还是停止。



$(error <text ...> )



产生一个致命的错误,<text ...>是错误信息。注意,error函数不会在一被使用就会产生错误信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也是可以的。例如:



示例一:

ifdef ERROR_001

$(error error is $(ERROR_001))

endif



示例二:

ERR = $(error found an error!)

.PHONY: err

err: ; $(ERR)



示例一会在变量ERROR_001定义了后执行时产生error调用,而示例二则在目录err被执行时才发生error调用。



$(warning <text ...> )



这个函数很像error函数,只是它并不会让make退出,只是输出一段警告信息,而make继续执行。

make 的运行
——————

一般来说,最简单的就是直接在命令行下输入make命令,make命令会找当前目录的makefile来执行,一切都是自动的。但也有时你也许只想让make重编译某些文件,而不是整个工程,而又有的时候你有几套编译规则,你想在不同的时候使用不同的编译规则,等等。本章节就是讲述如何使用make命令的。

一、make的退出码

make命令执行后有三个退出码:

0 —— 表示成功执行。
1 —— 如果make运行时出现任何错误,其返回1。
2 —— 如果你使用了make的“-q”选项,并且make使得一些目标不需要更新,那么返回2。

Make的相关参数我们会在后续章节中讲述。


二、指定Makefile

前面我们说过,GNU make找寻默认的Makefile的规则是在当前目录下依次找三个文件——“GNUmakefile”、“makefile”和“Makefile”。其按顺序找这三个文件,一旦找到,就开始读取这个文件并执行。

当前,我们也可以给make命令指定一个特殊名字的Makefile。要达到这个功能,我们要使用make的“-f”或是“--file”参数(“--makefile”参数也行)。例如,我们有个makefile的名字是“hchen.mk”,那么,我们可以这样来让make来执行这个文件:

make –f hchen.mk

如果在make的命令行是,你不只一次地使用了“-f”参数,那么,所有指定的makefile将会被连在一起传递给make执行。


三、指定目标

一般来说,make的最终目标是makefile中的第一个目标,而其它目标一般是由这个目标连带出来的。这是make的默认行为。当然,一般来说,你的makefile中的第一个目标是由许多个目标组成,你可以指示make,让其完成你所指定的目标。要达到这一目的很简单,需在make命令后直接跟目标的名字就可以完成(如前面提到的“make clean”形式)

任何在makefile中的目标都可以被指定成终极目标,但是除了以“-”打头,或是包含了“=”的目标,因为有这些字符的目标,会被解析成命令行参数或是变量。甚至没有被我们明确写出来的目标也可以成为make的终极目标,也就是说,只要make可以找到其隐含规则推导规则,那么这个隐含目标同样可以被指定成终极目标。

有一个make的环境变量叫“MAKECMDGOALS”,这个变量中会存放你所指定的终极目标的列表,如果在命令行上,你没有指定目标,那么,这个变量是空值。这个变量可以让你使用在一些比较特殊的情形下。比如下面的例子:

sources = foo.c bar.c
ifneq ( $(MAKECMDGOALS),clean)
include $(sources:.c=.d)
endif

基于上面的这个例子,只要我们输入的命令不是“make clean”,那么makefile会自动包含“foo.d”和“bar.d”这两个makefile。

使用指定终极目标的方法可以很方便地让我们编译我们的程序,例如下面这个例子:

.PHONY: all
all: prog1 prog2 prog3 prog4

从这个例子中,我们可以看到,这个makefile中有四个需要编译的程序——“prog1”, “prog2”, “prog3”和 “prog4”,我们可以使用“make all”命令来编译所有的目标(如果把all置成第一个目标,那么只需执行“make”),我们也可以使用“make prog2”来单独编译目标“prog2”。

即然make可以指定所有makefile中的目标,那么也包括“伪目标”,于是我们可以根据这种性质来让我们的makefile根据指定的不同的目标来完成不同的事。在Unix世界中,软件发布时,特别是GNU这种开源软件的发布时,其makefile都包含了编译、安装、打包等功能。我们可以参照这种规则来书写我们的makefile中的目标。

“all”
这个伪目标是所有目标的目标,其功能一般是编译所有的目标。
“clean”
这个伪目标功能是删除所有被make创建的文件。
“install”
这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。
“print”
这个伪目标的功能是例出改变过的源文件。
“tar”
这个伪目标功能是把源程序打包备份。也就是一个tar文件。
“dist”
这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。
“TAGS”
这个伪目标功能是更新所有的目标,以备完整地重编译使用。
“check”和“test”
这两个伪目标一般用来测试makefile的流程。

当然一个项目的makefile中也不一定要书写这样的目标,这些东西都是GNU的东西,但是我想,GNU搞出这些东西一定有其可取之处(等你的UNIX下的程序文件一多时你就会发现这些功能很有用了),这里只不过是说明了,如果你要书写这种功能,最好使用这种名字命名你的目标,这样规范一些,规范的好处就是——不用解释,大家都明白。而且如果你的makefile中有这些功能,一是很实用,二是可以显得你的makefile很专业(不是那种初学者的作品)。

作者: compare2000    时间: 2014-06-24 16:56
四、检查规则

有时候,我们不想让我们的makefile中的规则执行起来,我们只想检查一下我们的命令,或是执行的序列。于是我们可以使用make命令的下述参数:

“-n”
“--just-print”
“--dry-run”
“--recon”
不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行,这些参数对于我们调试makefile很有用处。

“-t”
“--touch”
这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态。

“-q”
“--question”
这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印出一条出错信息。

“-W <file>”
“--what-if=<file>”
“--assume-new=<file>”
“--new-file=<file>”
这个参数需要指定一个文件。一般是是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所发生的规则命令。

另外一个很有意思的用法是结合“-p”和“-v”来输出makefile被执行时的信息(这个将在后面讲述)。


五、make的参数

下面列举了所有GNU make 3.80版的参数定义。其它版本和产商的make大同小异,不过其它产商的make的具体参数还是请参考各自的产品文档。

“-b”
“-m”
这两个参数的作用是忽略和其它版本make的兼容性。

“-B”
“--always-make”
认为所有的目标都需要更新(重编译)。

“-C <dir>”
“--directory=<dir>”
指定读取makefile的目录。如果有多个“-C”参数,make的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。如:“make –C ~hchen/test –C prog”等价于“make –C ~hchen/test/prog”。

“—debug[=<options>]”
输出make的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。下面是<options>的取值:
a —— 也就是all,输出所有的调试信息。(会非常的多)
b —— 也就是basic,只输出简单的调试信息。即输出不需要重编译的目标。
v —— 也就是verbose,在b选项的级别之上。输出的信息包括哪个makefile被解析,不需要被重编译的依赖文件(或是依赖目标)等。
i —— 也就是implicit,输出所以的隐含规则。
j —— 也就是jobs,输出执行规则中命令的详细信息,如命令的PID、返回码等。
m —— 也就是makefile,输出make读取makefile,更新makefile,执行makefile的信息。

“-d”
相当于“--debug=a”。

“-e”
“--environment-overrides”
指明环境变量的值覆盖makefile中定义的变量的值。

“-f=<file>”
“--file=<file>”
“--makefile=<file>”
指定需要执行的makefile。

“-h”
“--help”
显示帮助信息。

“-i”
“--ignore-errors”
在执行时忽略所有的错误。

“-I <dir>”
“--include-dir=<dir>”
指定一个被包含makefile的搜索目标。可以使用多个“-I”参数来指定多个目录。

“-j [<jobsnum>]”
“--jobs[=<jobsnum>]”
指同时运行命令的个数。如果没有这个参数,make运行命令时能运行多少就运行多少。如果有一个以上的“-j”参数,那么仅最后一个“-j”才是有效的。(注意这个参数在MS-DOS中是无用的)

“-k”
“--keep-going”
出错也不停止运行。如果生成一个目标失败了,那么依赖于其上的目标就不会被执行了。

“-l <load>”
“--load-average[=<load]”
“—max-load[=<load>]”
指定make运行命令的负载。

“-n”
“--just-print”
“--dry-run”
“--recon”
仅输出执行过程中的命令序列,但并不执行。

“-o <file>”
“--old-file=<file>”
“--assume-old=<file>”
不重新生成的指定的<file>,即使这个目标的依赖文件新于它。

“-p”
“--print-data-base”
输出makefile中的所有数据,包括所有的规则和变量。这个参数会让一个简单的makefile都会输出一堆信息。如果你只是想输出信息而不想执行makefile,你可以使用“make -qp”命令。如果你想查看执行makefile前的预设变量和规则,你可以使用“make –p –f /dev/null”。这个参数输出的信息会包含着你的makefile文件的文件名和行号,所以,用这个参数来调试你的makefile会是很有用的,特别是当你的环境变量很复杂的时候。

“-q”
“--question”
不运行命令,也不输出。仅仅是检查所指定的目标是否需要更新。如果是0则说明要更新,如果是2则说明有错误发生。

“-r”
“--no-builtin-rules”
禁止make使用任何隐含规则。

“-R”
“--no-builtin-variabes”
禁止make使用任何作用于变量上的隐含规则。

“-s”
“--silent”
“--quiet”
在命令运行时不输出命令的输出。

“-S”
“--no-keep-going”
“--stop”
取消“-k”选项的作用。因为有些时候,make的选项是从环境变量“MAKEFLAGS”中继承下来的。所以你可以在命令行中使用这个参数来让环境变量中的“-k”选项失效。

“-t”
“--touch”
相当于UNIX的touch命令,只是把目标的修改日期变成最新的,也就是阻止生成目标的命令运行。

“-v”
“--version”
输出make程序的版本、版权等关于make的信息。

“-w”
“--print-directory”
输出运行makefile之前和之后的信息。这个参数对于跟踪嵌套式调用make时很有用。

“--no-print-directory”
禁止“-w”选项。

“-W <file>”
“--what-if=<file>”
“--new-file=<file>”
“--assume-file=<file>”
假定目标<file>需要更新,如果和“-n”选项使用,那么这个参数会输出该目标更新时的运行动作。如果没有“-n”那么就像运行UNIX的“touch”命令一样,使得<file>的修改时间为当前时间。

“--warn-undefined-variables”
只要make发现有未定义的变量,那么就输出警告信息。

隐含规则
————

在我们使用Makefile时,有一些我们会经常使用,而且使用频率非常高的东西,比如,我们编译C/C++的源程序为中间目标文件(Unix下是[.o]文件,Windows下是[.obj]文件)。本章讲述的就是一些在Makefile中的“隐含的”,早先约定了的,不需要我们再写出来的规则。

“隐含规则”也就是一种惯例,make会按照这种“惯例”心照不喧地来运行,那怕我们的Makefile中没有书写这样的规则。例如,把[.c]文件编译成[.o]文件这一规则,你根本就不用写出来,make会自动推导出这种规则,并生成我们需要的[.o]文件。

“隐含规则”会使用一些我们系统变量,我们可以改变这些系统变量的值来定制隐含规则的运行时的参数。如系统变量“CFLAGS”可以控制编译时的编译器参数。

我们还可以通过“模式规则”的方式写下自己的隐含规则。用“后缀规则”来定义隐含规则会有许多的限制。使用“模式规则”会更回得智能和清楚,但“后缀规则”可以用来保证我们Makefile的兼容性。
我们了解了“隐含规则”,可以让其为我们更好的服务,也会让我们知道一些“约定俗成”了的东西,而不至于使得我们在运行Makefile时出现一些我们觉得莫名其妙的东西。当然,任何事物都是矛盾的,水能载舟,亦可覆舟,所以,有时候“隐含规则”也会给我们造成不小的麻烦。只有了解了它,我们才能更好地使用它。


一、使用隐含规则

如果要使用隐含规则生成你需要的目标,你所需要做的就是不要写出这个目标的规则。那么,make会试图去自动推导产生这个目标的规则和命令,如果make可以自动推导生成这个目标的规则和命令,那么这个行为就是隐含规则的自动推导。当然,隐含规则是make事先约定好的一些东西。例如,我们有下面的一个Makefile:

foo : foo.o bar.o
cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)

我们可以注意到,这个Makefile中并没有写下如何生成foo.o和bar.o这两目标的规则和命令。因为make的“隐含规则”功能会自动为我们自动去推导这两个目标的依赖目标和生成命令。

make会在自己的“隐含规则”库中寻找可以用的规则,如果找到,那么就会使用。如果找不到,那么就会报错。在上面的那个例子中,make调用的隐含规则是,把[.o]的目标的依赖文件置成[.c],并使用C的编译命令“cc –c $(CFLAGS) [.c]”来生成[.o]的目标。也就是说,我们完全没有必要写下下面的两条规则:

foo.o : foo.c
cc –c foo.c $(CFLAGS)
bar.o : bar.c
cc –c bar.c $(CFLAGS)

因为,这已经是“约定”好了的事了,make和我们约定好了用C编译器“cc”生成[.o]文件的规则,这就是隐含规则。

当然,如果我们为[.o]文件书写了自己的规则,那么make就不会自动推导并调用隐含规则,它会按照我们写好的规则忠实地执行。

还有,在make的“隐含规则库”中,每一条隐含规则都在库中有其顺序,越靠前的则是越被经常使用的,所以,这会导致我们有些时候即使我们显示地指定了目标依赖,make也不会管。如下面这条规则(没有命令):

foo.o : foo.p

依赖文件“foo.p”(Pascal程序的源文件)有可能变得没有意义。如果目录下存在了“foo.c”文件,那么我们的隐含规则一样会生效,并会通过“foo.c”调用C的编译器生成foo.o文件。因为,在隐含规则中,Pascal的规则出现在C的规则之后,所以,make找到可以生成foo.o的C的规则就不再寻找下一条规则了。如果你确实不希望任何隐含规则推导,那么,你就不要只写出“依赖规则”,而不写命令。


二、隐含规则一览

这里我们将讲述所有预先设置(也就是make内建)的隐含规则,如果我们不明确地写下规则,那么,make就会在这些规则中寻找所需要规则和命令。当然,我们也可以使用make的参数“-r”或“--no-builtin-rules”选项来取消所有的预设置的隐含规则。

当然,即使是我们指定了“-r”参数,某些隐含规则还是会生效,因为有许多的隐含规则都是使用了“后缀规则”来定义的,所以,只要隐含规则中有“后缀列表”(也就一系统定义在目标.SUFFIXES的依赖目标),那么隐含规则就会生效。默认的后缀列表是:.out, .a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, .def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch .web, .sh, .elc, .el。具体的细节,我们会在后面讲述。

还是先来看一看常用的隐含规则吧。

1、编译C程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.c”,并且其生成命令是“$(CC) –c $(CPPFLAGS) $(CFLAGS)”

2、编译C++程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.cc”或是“<n>.C”,并且其生成命令是“$(CXX) –c $(CPPFLAGS) $(CFLAGS)”。(建议使用“.cc”作为C++源文件的后缀,而不是“.C”)

3、编译Pascal程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.p”,并且其生成命令是“$(PC) –c $(PFLAGS)”。

4、编译Fortran/Ratfor程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.r”或“<n>.F”或“<n>.f”,并且其生成命令是:
“.f” “$(FC) –c $(FFLAGS)”
“.F” “$(FC) –c $(FFLAGS) $(CPPFLAGS)”
“.f” “$(FC) –c $(FFLAGS) $(RFLAGS)”

5、预处理Fortran/Ratfor程序的隐含规则。
“<n>.f”的目标的依赖目标会自动推导为“<n>.r”或“<n>.F”。这个规则只是转换Ratfor或有预处理的Fortran程序到一个标准的Fortran程序。其使用的命令是:
“.F” “$(FC) –F $(CPPFLAGS) $(FFLAGS)”
“.r” “$(FC) –F $(FFLAGS) $(RFLAGS)”

6、编译Modula-2程序的隐含规则。
“<n>.sym”的目标的依赖目标会自动推导为“<n>.def”,并且其生成命令是:“$(M2C) $(M2FLAGS) $(DEFFLAGS)”。“<n.o>” 的目标的依赖目标会自动推导为“<n>.mod”,并且其生成命令是:“$(M2C) $(M2FLAGS) $(MODFLAGS)”。

7、汇编和汇编预处理的隐含规则。
“<n>.o” 的目标的依赖目标会自动推导为“<n>.s”,默认使用编译品“as”,并且其生成命令是:“$(AS) $(ASFLAGS)”。“<n>.s” 的目标的依赖目标会自动推导为“<n>.S”,默认使用C预编译器“cpp”,并且其生成命令是:“$(AS) $(ASFLAGS)”。

8、链接Object文件的隐含规则。
“<n>”目标依赖于“<n>.o”,通过运行C的编译器来运行链接程序生成(一般是“ld”),其生成命令是:“$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)”。这个规则对于只有一个源文件的工程有效,同时也对多个Object文件(由不同的源文件生成)的也有效。例如如下规则:

x : y.o z.o

并且“x.c”、“y.c”和“z.c”都存在时,隐含规则将执行如下命令:

cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x
rm -f x.o
rm -f y.o
rm -f z.o

如果没有一个源文件(如上例中的x.c)和你的目标名字(如上例中的x)相关联,那么,你最好写出自己的生成规则,不然,隐含规则会报错的。

9、Yacc C程序时的隐含规则。
“<n>.c”的依赖文件被自动推导为“n.y”(Yacc生成的文件),其生成命令是:“$(YACC) $(YFALGS)”。(“Yacc”是一个语法分析器,关于其细节请查看相关资料)

10、Lex C程序时的隐含规则。
“<n>.c”的依赖文件被自动推导为“n.l”(Lex生成的文件),其生成命令是:“$(LEX) $(LFALGS)”。(关于“Lex”的细节请查看相关资料)

11、Lex Ratfor程序时的隐含规则。
“<n>.r”的依赖文件被自动推导为“n.l”(Lex生成的文件),其生成命令是:“$(LEX) $(LFALGS)”。

12、从C程序、Yacc文件或Lex文件创建Lint库的隐含规则。
“<n>.ln” (lint生成的文件)的依赖文件被自动推导为“n.c”,其生成命令是:“$(LINT) $(LINTFALGS) $(CPPFLAGS) -i”。对于“<n>.y”和“<n>.l”也是同样的规则。


三、隐含规则使用的变量

在隐含规则中的命令中,基本上都是使用了一些预先设置的变量。你可以在你的makefile中改变这些变量的值,或是在make的命令行中传入这些值,或是在你的环境变量中设置这些值,无论怎么样,只要设置了这些特定的变量,那么其就会对隐含规则起作用。当然,你也可以利用make的“-R”或“--no–builtin-variables”参数来取消你所定义的变量对隐含规则的作用。

例如,第一条隐含规则——编译C程序的隐含规则的命令是“$(CC) –c $(CFLAGS) $(CPPFLAGS)”。Make默认的编译命令是“cc”,如果你把变量“$(CC)”重定义成“gcc”,把变量“$(CFLAGS)”重定义成“-g”,那么,隐含规则中的命令全部会以“gcc –c -g $(CPPFLAGS)”的样子来执行了。

我们可以把隐含规则中使用的变量分成两种:一种是命令相关的,如“CC”;一种是参数相的关,如“CFLAGS”。下面是所有隐含规则中会用到的变量:

1、关于命令的变量。

AR
函数库打包程序。默认命令是“ar”。
AS
汇编语言编译程序。默认命令是“as”。
CC
C语言编译程序。默认命令是“cc”。
CXX
C++语言编译程序。默认命令是“g++”。
CO
从 RCS文件中扩展文件程序。默认命令是“co”。
CPP
C程序的预处理器(输出是标准输出设备)。默认命令是“$(CC) –E”。
FC
Fortran 和 Ratfor 的编译器和预处理程序。默认命令是“f77”。
GET
从SCCS文件中扩展文件的程序。默认命令是“get”。
LEX
Lex方法分析器程序(针对于C或Ratfor)。默认命令是“lex”。
PC
Pascal语言编译程序。默认命令是“pc”。
YACC
Yacc文法分析器(针对于C程序)。默认命令是“yacc”。
YACCR
Yacc文法分析器(针对于Ratfor程序)。默认命令是“yacc –r”。
MAKEINFO
转换Texinfo源文件(.texi)到Info文件程序。默认命令是“makeinfo”。
TEX
从TeX源文件创建TeX DVI文件的程序。默认命令是“tex”。
TEXI2DVI
从Texinfo源文件创建军TeX DVI 文件的程序。默认命令是“texi2dvi”。
WEAVE
转换Web到TeX的程序。默认命令是“weave”。
CWEAVE
转换C Web 到 TeX的程序。默认命令是“cweave”。
TANGLE
转换Web到Pascal语言的程序。默认命令是“tangle”。
CTANGLE
转换C Web 到 C。默认命令是“ctangle”。
RM
删除文件命令。默认命令是“rm –f”。

2、关于命令参数的变量

下面的这些变量都是相关上面的命令的参数。如果没有指明其默认值,那么其默认值都是空。

ARFLAGS
函数库打包程序AR命令的参数。默认值是“rv”。
ASFLAGS
汇编语言编译器参数。(当明显地调用“.s”或“.S”文件时)。
CFLAGS
C语言编译器参数。
CXXFLAGS
C++语言编译器参数。
COFLAGS
RCS命令参数。
CPPFLAGS
C预处理器参数。( C 和 Fortran 编译器也会用到)。
FFLAGS
Fortran语言编译器参数。
GFLAGS
SCCS “get”程序参数。
LDFLAGS
链接器参数。(如:“ld”)
LFLAGS
Lex文法分析器参数。
PFLAGS
Pascal语言编译器参数。
RFLAGS
Ratfor 程序的Fortran 编译器参数。
YFLAGS
Yacc文法分析器参数。


四、隐含规则链

有些时候,一个目标可能被一系列的隐含规则所作用。例如,一个[.o]的文件生成,可能会是先被Yacc的[.y]文件先成[.c],然后再被C的编译器生成。我们把这一系列的隐含规则叫做“隐含规则链”。

在上面的例子中,如果文件[.c]存在,那么就直接调用C的编译器的隐含规则,如果没有[.c]文件,但有一个[.y]文件,那么Yacc的隐含规则会被调用,生成[.c]文件,然后,再调用C编译的隐含规则最终由[.c]生成[.o]文件,达到目标。

我们把这种[.c]的文件(或是目标),叫做中间目标。不管怎么样,make会努力自动推导生成目标的一切方法,不管中间目标有多少,其都会执着地把所有的隐含规则和你书写的规则全部合起来分析,努力达到目标,所以,有些时候,可能会让你觉得奇怪,怎么我的目标会这样生成?怎么我的makefile发疯了?

在默认情况下,对于中间目标,它和一般的目标有两个地方所不同:第一个不同是除非中间的目标不存在,才会引发中间规则。第二个不同的是,只要目标成功产生,那么,产生最终目标过程中,所产生的中间目标文件会被以“rm -f”删除。

通常,一个被makefile指定成目标或是依赖目标的文件不能被当作中介。然而,你可以明显地说明一个文件或是目标是中介目标,你可以使用伪目标“.INTERMEDIATE”来强制声明。(如:.INTERMEDIATE : mid )

你也可以阻止make自动删除中间目标,要做到这一点,你可以使用伪目标“.SECONDARY”来强制声明(如:.SECONDARY : sec)。你还可以把你的目标,以模式的方式来指定(如:%.o)成伪目标“.PRECIOUS”的依赖目标,以保存被隐含规则所生成的中间文件。

在“隐含规则链”中,禁止同一个目标出现两次或两次以上,这样一来,就可防止在make自动推导时出现无限递归的情况。

Make会优化一些特殊的隐含规则,而不生成中间文件。如,从文件“foo.c”生成目标程序“foo”,按道理,make会编译生成中间文件“foo.o”,然后链接成“foo”,但在实际情况下,这一动作可以被一条“cc”的命令完成(cc –o foo foo.c),于是优化过的规则就不会生成中间文件。

作者: compare2000    时间: 2014-06-24 16:58
java工程fortify失败的最终解决方案;ant、maven相关的fortify检查失败  
fortify java的编译脚本,不论ant、maven都写成如下形式。注意红色字体的地方,这个脚本是终极解决方案。。。

rem 工程名,任意英文名,不要有空格
SET BUILD_ID=my_build
rem 依赖的jar包
SET CLASSPATH=D:\software\apache-maven-3.0\Repo_eUPP\**\*.jar
rem 扫描的工程目录
SET SCAN_DIR=D:\code\eupp V01R01\eUPP-pkg\**\*.java
sourceanalyzer "-b" "%BUILD_ID%" –clean
rem 1.6是指java版本,1.7的话请写1.7
sourceanalyzer "-b" "%BUILD_ID%" "-machine-output" "-cp" "%CLASSPATH%" "-source" "1.6" "%SCAN_DIR%" "-Xmx1024m"

命令打可执行jar包:

        1.将源文件拷贝到d:\jartest;

        2.在cmd命令下进入jartest该目录;

           配置:
                set path=C盘下jdk1.5.0_05\bin所在的目录   --你自己的jdk安装目录
                set classpath=.      --当前路径

           执行命令javac -d . 类.java -----(编译class文件)

        3.删除源文件;

        4.jar -cvf tt.jar ./  -----(此处打包成不可执行jar包  tt为打包后的名字  ./为整个当前目录 指定要打包的文件)

        5.jar -xvf tt.jar     -----解压

        6.删除tt.jar

        7.修改META-INF下的MAMFEST.MF文件,追加一句

           Main-Class: <插入一空格>包路径.类名<回车>     -----此包路径为package后的路径  以.隔开

           (如果有第三方的jar文件 再加上 classpath:lib/a.jar   lib/b.jar  lib目录下面放要第三方jar文件)

        8.jar -cvfM tt.jar *  -----打包

        9.java -jar tt.jar   -----命令运行jar包   也可双击击运行



        我们在打包时,只要不在当前路径下进行打包,对其它路径下的一个目录进行打包,往往含他自己的路径,可以用下面的命令去掉那些路径:

      第一种:jar   cvfm  test.jar   c:\manifest.mf  -C  c:\test   \     注意:后面的 \ 可以换成点

      第二种:jar   cvf     test.jar   -C  c:\test  \   注意:后面的 \ 可以换成点

      第三种:jar   -cvf    test.jar  -C   c:\test  \   注意:后面的 \ 可以换成点



打包可执行jar文件的一些注意事项:
      jar cvfm [目标.jar] [MANIFEST文件名] [应用程序所在目录]

      MANIFEST文件名随便,但jar参数中的"m"不可少,否则在jar文件中你会看到一个只有版本号的MANIFEST.MF文件。

      MANIFEST文件,也叫标明文件,清单文件,用来记录jar文件的相关信息。为了打包可执行jar文件,必须创建带jar文件主类的信息的MANIFEST文件。在任意位置,如:E:\temp,创建名为myManifest的文件,用文本编辑器编辑该文件,加入下行:

       Main-Class: 应用程序主类的路径名+回车

       回车是必须的,否则MANIFEST.MF中只有版本号。主类的路径名如:com.AppMain (假设在E:\temp\中有个com   目 录,com中有个属于com包的AppMain.class)

应用程序所在目录,当然就是com啦,如果我们在e:\temp下打包,则可以输入:

       jar cvfm AppMain.jar myManifest com





#压缩java文件, 打成war包

1.打开要打包的文件夹(cd 目录)
2.配置
    set path=C盘下jdk1.5.0_05\bin所在的目录 --你自己的jdk安装目录
    set classpath=. 当前路径
3.jar cvf tt.war ./    --(./表示当前文件下所有文件,要有   命令格式:java cvf 打包文件名称 要打包的目录 打包文件保存路径)

4.解压自然就是:  jar xvf temp.war



在包涵第三方jar包情况下在eclipse下打jar包:

    在你的项目文件夹下建一个META-INF文件夹里面新建一个

     MANIFEST.MF的文件内容大至如下
       Manifest-Version: 1.0
       Ant-Version: Apache Ant 1.6.2
       Created-By: 1.5.0_06-57 ("Apple Computer, Inc.")
       Main-Class: com.opensymphony.workflow.designer.Launcher
      Class-Path: looks.jar forms.jar syntax.jar jgraph.jar foxtrot.jar osworkflow-2.8.0.jar oscore-2.2.5.jar

     Main-Class就是你要运行的类。
     Class-path:就是你要引入的包
     用eclipse export导出jar文件里,选择
         user existing manifest from workspace
         manifest file:/你的项目名/src/META-INF/MANIFEST.MF
         点击完成。这样应该就可以了

作者: compare2000    时间: 2014-07-30 16:42
本帖最后由 compare2000 于 2014-07-30 16:59 编辑

常用Git,Repo命令  
Repo

repo init -u ssh://android.huawei.com/platform/manifest.git -b hw/platform/jellybean/K3V200_D2_4.1.1_r1/U9701L --no-repo-verify --repo-branch=stable

repo forall -c "git xxxx"            //对所有的git仓执行git命令

repo upload                           //提交commit

repo upload .                      //提交当前git仓的commit

repo --trace upload             //提交并打印出过程

repo sync                         //同步代码,拉代码

repo sync "git仓名"             //单同步某个git仓,或单拉某个仓

repo sync .                         //单同步当前的git仓





Git使用指南
Refer to How to version projects with Git



1.创建目录

$ git config --global user.name "Your Name Comes Here"

$ git config --global user.email you@yourdomain.example.com

$ git config --list 查看相关信息

$ git init

如果作为 Server 存在,那么可以忽略工作目录,以纯代码仓库形式存在。

$ git --bare init

可以在~/.gitconfig设置别名

[alias]
st = status
ci = commit -a
co = checkout  

2.文件操作

$ git add .

$ git add file1 file2 file3

$ git add -u

$ git add -p // 为你做的每次修改,Git将为你展示变动的代码,并询问该变动是否应是下一次提交的一部分。回答“y”或者“n”。也有其他选项,比如延迟决定:键入“?”来学习更多。

$ git rm file1 file2 file3

$ git mv file1 file2

3.提交更改

$ git commit -a -m ' '

$ git commit --amend -a -m ' ' // 修改上一次的信息,不作为新的提交

$ git stash // 保存当前草稿,便于切换分支

$ git stash pop // 恢复草稿

$ git stash apply

$ git stash list

$ git stash apply stash@{1}

$ git stash clear

4.撤销更改

$ git reset HEAD file1 // 取消暂存区的文件快照(即恢复成最后一个提交版本),这不会影响工作目录的文件修改。

$ git reset --hard HEAD^ // 将整个项目回溯到以前的某个版本,可以使用 "git reset"。可以选择的参数包括默认的 "--mixed" 和 "--hard",前者不会取消工作目录的修改,而后者则放弃全部的修改。该操作会丢失其后的日志


$ git checkout -- file1 // 使用暂存区快照恢复工作目录文件,工作目录的文件修改被抛弃。

$ git checkout HEAD^ file1 // 直接 "签出" 代码仓库中的某个文件版本到工作目录,该操作同时会取消暂存区快照。

$ git checkout "@{10 minutes ago}" // 直接 "签出" 10分钟之前代码仓库中的某个文件版本到工作目录,该操作同时会取消暂存区快照。

$ git checkout "@{5}" // 直接 "签出" 倒数第五次保存的某个文件版本到工作目录,该操作同时会取消暂存区快照。

$ git revert SHA1_HASH // 还原特定哈希值对应的提交。该还原记录作为一个新的提交。

5.查看历史纪录或者当前状态

$ git log

$ git log -p

$ git log -U 显示commit的详细信息

$ git log --stat --summary

$ git log V3..V7 //显示V3之后直至V7的所有历史记录

$ git log V3.. //显示所有V3之后的历史记录。注意<since>..<until>中任何一个被省略都将被默认设置为HEAD。所以如果使用..<until>的话,git log在大部分情况下会输出空的。

$ git log –since=”2 weeks ago” //显示2周前到现在的所有历史记录。具体语法可查询git-ref-parse命令的帮助文件。

$ git log stable..experimental //将显示在experimental分支但不在stable分支的历史记录

$ git log experimental..stable //将显示在stable分支但不在experimental分支的历史记录

$ git log -S'你要找的内容',就可以从全部的历史纪录,瞬間找到你要找的东西


$ git blame FILE // 标注出一个指定的文件里每一行内容的最后修改者,和最后修改时间。

$ git diff // 这个命令只在git add之前使用有效。如果已经add了,那么此命令输出为空

$ git diff –cached // 这个命令在git add之后在git commit之前有效。

$ git diff "@{yesterday}" // 比较当前和昨天的内容

$ git status // 这个命令在git commit之前有效,表示都有哪些文件发生了改动



$ git show 5b888 // 使用git show再加上述的commit名称来显式更详细的commit信息

$ git show master // 显示分支信息

$ git show HEAD // 使用HEAD字段可以代表当前分支的头(也就是最近一次commit)

$ git show HEAD^ //查看HEAD的父母的信息, 可以使用^表示parent

$ git show HEAD^^ //查看HEAD的父母的父母的信息

$ git show HEAD~4 //查看HEAD上溯4代的信息


$ git tag V3 5b888 //以后可以用V3来代替复杂的名称(5b888…)

$ git show V3

$ git branch stable V3 //建立一个基于V3的分支

$ git grep “print” V3 //在V3中搜索所有的包含print的行

$ git grep “print” //在所有的历史记录中搜索包含print的行


6.协作操作


$ git clone git://server/path/to/files // Git deamon

$ git clone your.computer:/path/to/script  or git clone ssh://car.colorado.edu/home/xxx ./xxxxx // SSH
        // SSH

$ git pull

$ git push // 在将代码提交(push)到服务器之前,首先要确认相关更新已经合并(merge)到主分支(master)。还应该先从服务器刷新(pull)最新代码,以确保自己的提交不会和别人最新提交的代码冲突。

如果想在merge前先查看更改:

$ git fetch /home/bob/myrepo master:bobworks //此命令意思是提取出bob修改的代码内容,然后放到我(rocrocket)工作目录下的bobworks分支中。之所以要放到分支中,而不是master中,就是要我先仔仔细细看看bob的开发成果,如果我觉得满意,我再merge到master中,如果不满意,我完全可以直接git branch -D掉。

$ git whatchanged -p master..bobworks //用来查看bob都做了什么

$ git checkout master //切换到master分区

$ git pull . bobworks //如果我检查了bob的工作后很满意,就可以用pull来将bobworks分支合并到我的项目中了

7.分支管理

$ git branch: 查看当前分支

$ git checkout -b/branch experimental: 创建新分支

$ git checkout experimental: 切换到另一分支

$ git merge experimental:合并分支

$ git branch -d experimental:删除分支, 使用的是小写的-d,表示“在分支已经合并到主干后删除分支”。

$ git branch -D experimental:删除分支, 表示“不论如何都删除分支”,-D使用在“分支被证明失败”

8.补丁工作

git format-patch:当你想给一个开源项目(例如Rails)提交一段代码的时候,或者你想给小组成员展示一段你并不想提交的代码,那么你还是需要 patch的,Git的'format-patch'命令良好的支持了这个功能。
第一,利用branch命令 创建一个分支;
第二,修改你的代码;
第三,在该分支上提交你的修改;
第四,使用'git format-patch'命令来生成一个patch文件,例如:'git format-patch master --stdout > ~/Desktop/tmp.patch'就是将工作分支与master主干的不同,存放在'~/Desktop'文件夹下,生成一个叫做 tmp.patch的文件(另一种简单的版本是利用diff命令,例如'git diff ..master > ~/Desktop/tmp.patch'),这样就生成了patch文件。那么别人就可以使用'git apply'命令来应用patch,例如'git apply ~/Desktop/tmp.patch'就是将patch打在当前的工作分支上

9.仓库维护

$ git fsck: 不加–full参数的情况下,这个命令一般会以非常低廉的代价确保仓库在一个不错的健康状态之中。

$ git count-objects: 统计有多少松散的对象,没有 repack 的对象消耗了多少硬盘空间。

$ git gc: 在本地仓库进行 repack,并进行其他日常维护工作。

$ git filter-branch --tree-filter `rm top/secret/file` HEAD //在所有记录中永久删除某个文件

$ git rebase -i HEAD~10 // 后10个提交会出现在你喜爱的$EDITOR。通过删除行来移去提交。通过为行重新排序来为提交重新排序。用“edit”来替换“pick”来标志一个提交可修改。用“squash”来替换“pick”来将一个提交和前一个合并。

10.错误查询

刚刚发现程序里有一个功能出错了,即使你过去经常提交变更,Git还是可以精确的找出问题所在:

$ git bisect start

$ git bisect bad SHA1_OF_BAD_VERSION

$ git bisect good SHA1_OF_GOOD_VERSION

Git从历史记录中检出一个中间的状态,在这个状态上测试功能,如果还是错误的:

$ git bisect bad

如果可以工作了,则把"bad"替换成"good"。 Git会再次帮你找到一个以确定的好版本和坏版本之间的状态,经过一系列的迭代,这种二进制查询会帮你找到导致这个错误的那次提交。一旦完成了问题定位的调查,你可以返回到原始状态,键入:

$ git bisect reset







不需要手工测试每一次改动,执行如下命令可以自动的完成上面的搜索:

$ git bisect run COMMAND

Git使用指定命令(通常是一个一次性的脚本)的返回值来决定一次改动是否是正确的:命令退出时的代码0代表改动是正确的,125代表要跳过对这次改动的检查,1到127之间的其他数值代表改动是错误的。返回负数将会中断整个bisect的检查。
另外推荐git资料:http://gitbook.liuhui998.com/book.pdf

shell中可能经常能看到:>/dev/null 2>&1


分解这个组合:“>/dev/null 2>&1” 为五部分。



1:> 代表重定向到哪里,例如:echo "123" > /home/123.txt

2:/dev/null 代表空设备文件
3:2> 表示stderr标准错误
4:&1, shell 语法,无论文件描述符1在什么地方
5:1 表示stdout标准输出,系统默认值是1,所以">/dev/null"等同于 "1>/dev/null"



因此,>/dev/null 2>&1也可以写成“1> /dev/null 2> &1”




那么本文标题的语句执行过程为:

1>/dev/null :首先表示标准输出重定向到空设备文件,也就是不输出任何信息到终端,说白了就是不显示任何信息。
2>&1 :接着,标准错误输出重定向 到 标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件。
说清楚了吗,大家理解下吧!
顺便对比述说下这么用的好处!
   最常用的方式有:
command > file 2>file  与command > file 2>&1
它们 有什么不同的地方吗?

      首先command > file 2>file 的意思是将命令所产生的标准输出信息,和错误的输出信息送到file 中.command  > file 2>file 这样的写法,stdout和stderr都直接送到file中, file会被打开两次,这样stdout和stderr会互相覆盖,这样写相当使用了FD1和FD2两个同时去抢占file 的管道。
      而command >file 2>&1 这条命令就将stdout直接送向file, stderr 继承了FD1管道后,再被送往file,此时,file 只被打开了一次,也只使用了一个管道FD1,它包括了stdout和stderr的内容。

      从IO效率上,前一条命令的效率要比后面一条的命令效率要低,所以在编写shell脚本的时候,较多的时候我们会command > file 2>&1 这样的写法。




思考题:

$ cmd 2>&1 1>/dev/null

结果是怎么样?





---------------------------------

答案:后一种只输出了STDERR到STDOUT,STDOUT的被输入到了/dev/null,这样就只有STDERR显示出来了。



原因是shell处理重定向是从左到右,所以先处理2>&1,处理完了之后,再处理1>/dev/null
grep 查找特定文件的内容  grep -R --include="*.cpp" key dir

上述命令的含义:

在dir目录下递归查找所有.cpp文件中的关键字key

Git stash --- Git 暂存  Git stash应用场景:

在当前branch上开发到任意时刻,有一个突发的问题需要解决,但当前的开发还不能提交,这时,可以先用stash将当前的开发暂存起来,然后回到当前的HEAD,进行突发任务的开发,将当前突发任务开发提交之后,在将暂存的工作拿出来,接着开发。



步骤:



1,$git stash//暂存当前开发,这时候,查看文件状态,是干净的

2,开发

3,提交开发

4,取出暂存

    $git stash pop

      //可能会有conflict,需要resolve

如何识别linux 版本号  动态方式:
#include <linux/utsname.h>
utsname();
   
静态方式:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,12)
#include <linux/of_gpio.h>
#endif

通过sysrq查看进程和内核运行状态 技巧和方法名称 通过sysrq查看进程和内核运行状态
适用问题的范围 适用于冻屏,内核死锁等问题的定位,解决过的问题:
等级 高级
内容描述

如果系统出现挂起情况或者在诊断一些和内核相关,比较怪异,需要现场定位问题的时候,使用sysrq键是一个比较好的方式。


1. 如何打开和关闭SysRq组合键?

打开这个功能,运行:

adb shell

# echo 1 >> /proc/sys/kernel/sysrq

关闭这个功能:

# echo 0 >> /proc/sys/kernel/sysrq


2. 如何触发一个sysrq事件?

首先手机需要有root权限:

# echo 'w' >> /proc/sysrq-trigger

当我触发一个sysrq事件的时候,结果保存在什么地方?

通过adb shell cat /proc/kmsg > kmsg.txt

在Log中查找关键字sysrq可以看到相关内核信息:


sysrq功能被打开后,有几种sysrq事件可以被触发。不同的内核版本可能会有些不同。但有一些是共用的:

* m - 导出关于内存分配的信息

* w - 导出阻塞线程状态信息(可以定位一些内核驱动或线程死锁等问题)

* p - 到处当前CPU寄存器信息和标志位的信息

* c - 故意让系统崩溃(在使用netdump或者diskdump的时候有用)

* s - 立即同步所有挂载的文件系统

* u - 立即重新挂载所有的文件系统为只读

* b - 立即重新启动系统

* o - 立即关机(如果机器配置并支持此项功能)

详细请参看:

sysrq.txt文件,可以在工程搜索这个文件获取。



扩展及其注意事项

echo 'w' >> /proc/sysrq-trigger 输出结果

<6>[1217, sh] [  152.193682] SysRq : Show Blocked State

<6>[1217, sh] [  152.197344]   task                PC stack   pid father

<6>[1217, sh] [  152.203509] kworker/u:0     D c0778648  6400     5      2 0x00000000

<4>[1217, sh] [  152.210804] [<c0778648>] (__schedule+0x514/0x5f from [<c003be94>] (rr_read+0xa8/0x12

<4>[1217, sh] [  152.219838] [<c003be94>] (rr_read+0xa8/0x12 from [<c003ca28>] (do_read_data+0x18/0xb54)

<4>[1217, sh] [  152.228963] [<c003ca28>] (do_read_data+0x18/0xb54) from [<c0091b5c>] (process_one_work+0x27c/0x484)

<4>[1217, sh] [  152.238913] [<c0091b5c>] (process_one_work+0x27c/0x484) from [<c0091f74>] (worker_thread+0x210/0x3b0)

<4>[1217, sh] [  152.249076] [<c0091f74>] (worker_thread+0x210/0x3b0) from [<c0095e10>] (kthread+0x80/0x8c)

<4>[1217, sh] [  152.258293] [<c0095e10>] (kthread+0x80/0x8c) from [<c000f028>] (kernel_thread_exit+0x0/0x

<6>[1217, sh] [  152.267572] kworker/u:1     D c0778648  5320    45      2 0x00000000

<4>[1217, sh] [  152.274866] [<c0778648>] (__schedule+0x514/0x5f from [<c003be94>] (rr_read+0xa8/0x12

<4>[1217, sh] [  152.283900] [<c003be94>] (rr_read+0xa8/0x12 from [<c003ca28>] (do_read_data+0x18/0xb54)

<4>[1217, sh] [  152.292995] [<c003ca28>] (do_read_data+0x18/0xb54) from [<c0091b5c>] (process_one_work+0x27c/0x484)

<4>[1217, sh] [  152.302975] [<c0091b5c>] (process_one_work+0x27c/0x484) from [<c0091f74>] (worker_thread+0x210/0x3b0)

<4>[1217, sh] [  152.313139] [<c0091f74>] (worker_thread+0x210/0x3b0) from [<c0095e10>] (kthread+0x80/0x8c)

<4>[1217, sh] [  152.322356] [<c0095e10>] (kthread+0x80/0x8c) from [<c000f028>] (kernel_thread_exit+0x0/0x

<6>[1217, sh] [  152.331664] SurfaceFlinger  D c0778648  5712   538      1 0x00000000

<4>[1217, sh] [  152.338928] [<c0778648>] (__schedule+0x514/0x5f from [<c07765fc>] (schedule_timeout+0x28/0x344)

<4>[1217, sh] [  152.348725] [<c07765fc>] (schedule_timeout+0x28/0x344) from [<c0778d24>] (wait_for_common+0xf0/0x16

<4>[1217, sh] [  152.358889] [<c0778d24>] (wait_for_common+0xf0/0x168) from [<c02ea7a8>] (mipi_dsi_cmd_dma_tx+0x22c/0x2ac)

<4>[1217, sh] [  152.369388] [<c02ea7a8>] (mipi_dsi_cmd_dma_tx+0x22c/0x2ac) from [<c02ea910>] (mipi_dsi_cmds_tx+0xe8/0x188)

<4>[1217, sh] [  152.379978] [<c02ea910>] (mipi_dsi_cmds_tx+0xe8/0x188) from [<c02eba38>] (mipi_lcd_register_write+0xec/0x174)

<4>[1217, sh] [  152.390843] [<c02eba38>] (mipi_lcd_register_write+0xec/0x174) from [<c02ebaf8>] (process_mipi_table+0x38/0x7c)

<4>[1217, sh] [  152.401770] [<c02ebaf8>] (process_mipi_table+0x38/0x7c) from [<c02ed9ac>] (mipi_hx8369a_lcd_on+0xa0/0xec)

<4>[1217, sh] [  152.412269] [<c02ed9ac>] (mipi_hx8369a_lcd_on+0xa0/0xec) from [<c02eca54>] (panel_next_on+0x3c/0x48)

<4>[1217, sh] [  152.422340] [<c02eca54>] (panel_next_on+0x3c/0x48) from [<c02e8bf4>] (mipi_dsi_on+0x660/0x800)

<4>[1217, sh] [  152.431893] [<c02e8bf4>] (mipi_dsi_on+0x660/0x800) from [<c02eca54>] (panel_next_on+0x3c/0x48)

<4>[1217, sh] [  152.441446] [<c02eca54>] (panel_next_on+0x3c/0x48) from [<c02cd0cc>] (mdp_on+0x118/0x164)

<4>[1217, sh] [  152.450541] [<c02cd0cc>] (mdp_on+0x118/0x164) from [<c02c71e4>] (msm_fb_blank_sub+0x74/0xd8)

<4>[1217, sh] [  152.459911] [<c02c71e4>] (msm_fb_blank_sub+0x74/0xd8) from [<c02c7cf8>] (msm_fb_open+0xb8/0xfc)

<4>[1217, sh] [  152.469555] [<c02c7cf8>] (msm_fb_open+0xb8/0xfc) from [<c02c1550>] (fb_open+0xa8/0xe8)

<4>[1217, sh] [  152.478406] [<c02c1550>] (fb_open+0xa8/0xe8) from [<c012ce94>] (chrdev_open+0x10c/0x134)

<4>[1217, sh] [  152.487440] [<c012ce94>] (chrdev_open+0x10c/0x134) from [<c0127ab8>] (__dentry_open.isra.12+0x190/0x29c)

<4>[1217, sh] [  152.497848] [<c0127ab8>] (__dentry_open.isra.12+0x190/0x29c) from [<c013599c>] (do_last.isra.29+0x690/0x6c0)

<4>[1217, sh] [  152.508621] [<c013599c>] (do_last.isra.29+0x690/0x6c0) from [<c0135b94>] (path_openat+0xb8/0x35c)

<4>[1217, sh] [  152.518419] [<c0135b94>] (path_openat+0xb8/0x35c) from [<c0135f18>] (do_filp_open+0x2c/0x78)

<4>[1217, sh] [  152.527819] [<c0135f18>] (do_filp_open+0x2c/0x78) from [<c01287dc>] (do_sys_open+0xd8/0x170)

<4>[1217, sh] [  152.537189] [<c01287dc>] (do_sys_open+0xd8/0x170) from [<c000dec0>] (ret_fast_syscall+0x0/0x30)



echo 'm' >> /proc/sysrq-trigger 输出结果



<6>[1443, sh] [   34.173752] SysRq : Show Memory

<4>[1443, sh] [   34.176804] Mem-info:

<4>[1443, sh] [   34.180039] Normal per-cpu:

<4>[1443, sh] [   34.183763] CPU    0: hi:  186, btch:  31 usd:  74

<4>[1443, sh] [   34.189500] HighMem per-cpu:

<4>[1443, sh] [   34.193316] CPU    0: hi:   42, btch:   7 usd:  35

<4>[1443, sh] [   34.199053] active_anon:30965 inactive_anon:58 isolated_anon:0

<4>[1443, sh] [   34.199053]  active_file:2311 inactive_file:39075 isolated_file:0

<4>[1443, sh] [   34.199053]  unevictable:0 dirty:9 writeback:0 unstable:0

<4>[1443, sh] [   34.199053]  free:108103 slab_reclaimable:1861 slab_unreclaimable:2643

<4>[1443, sh] [   34.199053]  mapped:19383 shmem:67 pagetables:1890 bounce:0

<4>[1443, sh] [   34.233175] Normal free:432136kB min:3364kB low:4204kB high:5044kB active_anon:97972kB inactive_anon:192kB active_file:6416kB inactive_file:59080kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:708932kB mlocked:0kB dirty:36kB writeback:0kB mapped:43264kB shmem:192kB slab_reclaimable:7444kB slab_unreclaimable:10572kB kernel_stack:5064kB pagetables:7560kB unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? no

<4>[1443, sh] [   34.273676] lowmem_reserve[]: 0 1013 1013

<4>[1443, sh] [   34.278650] HighMem free:276kB min:128kB low:280kB high:432kB active_anon:25888kB inactive_anon:40kB active_file:2828kB inactive_file:97220kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:129740kB mlocked:0kB dirty:0kB writeback:0kB mapped:34268kB shmem:76kB slab_reclaimable:0kB slab_unreclaimable:0kB kernel_stack:0kB pagetables:0kB unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? no

<4>[1443, sh] [   34.317350] lowmem_reserve[]: 0 0 0

<4>[1443, sh] [   34.321776] Normal: 6*4kB 10*8kB 3*16kB 2*32kB 3*64kB 1*128kB 0*256kB 3*512kB 2*1024kB 1*2048kB 104*4096kB = 432152kB

<4>[1443, sh] [   34.333312] HighMem: 5*4kB 0*8kB 0*16kB 0*32kB 0*64kB 0*128kB 1*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 276kB

<4>[1443, sh] [   34.344422] 41449 total pagecache pages

<4>[1443, sh] [   34.349214] 0 pages in swap cache

<4>[1443, sh] [   34.353456] Swap cache stats: add 0, delete 0, find 0/0

<4>[1443, sh] [   34.359621] Free swap  = 0kB

<4>[1443, sh] [   34.363436] Total swap = 0kB

<4>[1443, sh] [   34.383946] 232448 pages of RAM

<4>[1443, sh] [   34.386998] 129615 free pages

<4>[1443, sh] [   34.390904] 12655 reserved pages

<4>[1443, sh] [   34.395086] 3020 slab pages

<4>[1443, sh] [   34.398809] 195010 pages shared

<4>[1443, sh] [   34.402899] 0 pages swap cached

作者: compare2000    时间: 2014-07-30 17:03
console有多种含义,本文仅针对printk输出的设备,驱动使用register_console注册一个console。 console和tty有很大区别:console是个只输出的设备,功能很简单,只能在内核中访问;tty是char设备,可以被用户程序访问。




实际的驱动比如串口对一个物理设备会注册两次,一个是tty,一个是console,并通过在console的结构中记录tty的主次设备号建立了联系。




在内核中,tty和console都可以注册多个。当内核命令行上指定console=ttyS0之类的参数时,首先确定了printk实际使用那个console作为输出,其次由于console和tty之间的对应关系,打开/dev/console时,就会映射到相应的tty上。用一句话说:/dev/console将映射到默认console对应的tty上。







/dev/console是系统控制台,或者说是物理控制台,而/dev/ttyn则是虚拟控制台,你可以传导诸如console=ttyS1的启动参数给内核,使得ttyS1(第二个串口控制台)成为系统控制台。如果没有传导这个参数给内核,内核就会自动寻找可用的设备作为系统控制台,首先是VGA显示卡,其次才是串口。




注册uart_driver 的时候会分配相应的tty_driver结构

这样通过/dev/console和具体的uart驱动联系起来,通过co->index,又和具体的uart设备联系了起来,这样,内核通过/dev/console就可以操作串口,进行内核信息的输出了。

内核使用/dev/console的地方为kernel初始化的时候:







http://developer.51cto.com/art/201209/357501.htm
我做行销这几年  前言

一个下雨的周末,独坐宿舍,出差随身带了本书,很无趣,难怪压在箱底一直没有看完。又一次深刻体会了,“耐劳碌易,安闲散难”。劳碌的时候,比如六月回国准备比电测试,每天凌晨才走回酒店睡觉,但是苦累过了,牢骚过了,觉得日子过得充实。这大概与“只问耕耘,不问收获”的感受类似。闲散时,体会到更多的无聊和寂寞。在欧洲生活,真的是要具备“自己和自己玩的能力”。

趁这个机会总结一下行销七年的体会吧。去年拿到“金牌产品经理”的“大铜牌”时,就想写个总结。结果拖到现在才开始动笔,总是拿“忙”做借口,其实只是个“合理”的理由而已。真实的原因只有自己清楚----还是得承认自己有轻微的拖延症,而且对自己的文字表达能力没有信心。总之,这次真的开始了,想到哪写到哪。



为什么想做行销?

2003年初毕业,在广州做研发,2005年进成都研究所做硬件开发,2007年转行销部门。原计划在行销待三年,现在已经是第七个年头了。谈不上成功,但这七年对自己意义重大,为人处事和自信心至少有了改善。也许,任何一个人不论从事什么职业,七年的时间都会发生很大变化。行销的工作可能只是加速了我的变化。

为什么从研发转行销呢,“成都可是个来了就不想走的城市”?不少人问过我这个问题。我自认为是个“内向”的人,木讷少言,和人沟通常常找不到话题,这样的人似乎不适合去面对客户。我对这个问题的回答一直是“我想改变自己”。是的,这是我发自内心的答案,不是个“听起来合理的答案”。

2007年上半年,看到公司内部招产品经理的通知,马上就有了这个想法。现在想,最主要的原因是对现状不满,感觉按这个性格发展下去,前途无望。**自己到一个必须和人打交道的地方,强迫自己改变。看过一个资料,说每次吃饭都吃完的人有强迫症。好吧,还得承认,我有轻微的强迫症。每次吃饭,不管是自助餐还是在家里,都努力吃到一粒米不剩。当然,实在吃不下除外,还没有强迫到自虐的程度。我儿子正好相反,每次都剩饭,完全继承了他妈的遗传。

总之,为了改变自己,决定转行销。也许有点自私,不记得当时是否考虑了孩子还小。应该是考虑了的,和老婆商量,老婆也支持我出去改变一下,同意出去三年。

好像也犹豫过,担心失去眼前的东西,也担心自己到一线能不能做好?一个工科生,毕业四年多,一直都是做硬件开发工作,和人打交道少。记得当时还约了老专家沟通,老专家最后给的结论是,“其实你内心已经做了决定”。在离开研发之前,有天中午和一个同事同桌吃饭,她和我说,“经常和以前去市场的朋友联系,其实市场和研发的收入差不多”。当时的主管也劝我,市场不像你想象的那么好。这些都没有影响我最终的决定,也许这就是我的性格,决定了的事情拉不回来,很难听进去别人的劝告。



什么样性格的人适合做行销?

我一直都以为,我的性格对做行销工作是个缺陷,很羡慕能在聚会期间谈笑风生、和客户打成一片的高手。记得2007年刚转行销,完成了二营,还在深圳部门培训期间。原部门领导到深圳出差,约了他一起吃饭,他也说“这次部门转行销的四个人,最没有想到的就是你”。

上个月,某产品线领导来比利时,和领导做了深入的沟通。从早上9点开始,边逛街边交流,包括个人的经历、做过的项目、对市场格局的一些看法等。中午一起吃饭,谈到性格的问题,领导很郑重的给了忠告,“我理解你的想法,你可能很羡慕那些能很快和人打成一片,善于搞客户关系的客户经理。你觉得这方面是你的劣势。其实靠做事长期建立起来的信任是真正能产生价值的。我希望你记住我给你的建议,在你以后的职业生涯中,这种性格是你的优势,不是劣势。不要试图去改变它,要利用它。”

这位领导在研发和市场工作多年,可以说阅人无数,很有经验。他是对的,任何性格都有好有坏。确实,在我长期接触的几个客户中,我和关键的架构师都建立了比较好的信任关系,离开了荷兰后还经常有联系,对工作产生了很大帮助。



机缘巧合到荷兰。

决定转行销之后就是和研发领导谈话,行销部门面试。都很顺利,因为研发有输出指标,正发愁没有人愿意去。2007年7月,从成都飞深圳,开始行销培训。在成都双流机场,碰到三个同事,也是这次转行销的,同一班飞机去深圳。

2007年7-10月在机关参加二营培训,看资料、学习产品知识、早起跑步、写总结、百草园门口吃烧烤,周末聚餐、玩杀人游戏。认识了不少各种各样的人,可惜后来分到全球各地后,逐渐疏于联系。10月底分配去向的时候,从成都一起来的兄弟居然都去了自己想去的地方。有个兄弟,受失恋的刺激,要去补助高的地方挣钱,如愿以偿去了非洲;有一个忘记是什么原因了,对南美情有独钟,分配去了智利;我一直定位自己是技术导向的,要求去对技术要求比较高的地方发挥优势,就来了西欧。

10月底,确定了到荷兰做光网产品经理。前任产品经理要马上去其他国家,我去接替她的工作。现在仍然记得那个日期,2007年11月2号,从香港飞荷兰。荷兰的同事很热心,早上7点多,开车到机场接我到有名的540宿舍,放下行李就去了办公室。相信这是会永远留在记忆中的地方,在这里度过了刚刚转行销迷茫的两年。夏天在院子里吃完饭,可以欣赏飞机从头顶很低的飞过,一分钟一架。



最大的障碍是语言。

11月份是荷兰的冬季了,当天是个阴天。第一天上班,开始做KPN国际波分网络设计工作。不记得太多事情了,只记得前任留下的“两个工作日内答复客户问题”的规则。介绍了一下网络设计、报价的规则,带着见了一次客户。两周之后她就离开了,我开始独立工作。

到荷兰之后的第二周,比利时需要一个光网产品经理去和卢森堡的客户做产品交流,不知道什么原因安排了我这个刚来一周的人去支持。记得自己出发前背了几遍波分主打胶片。到比利时很顺利,先从阿姆中心站做火车到比利时中心站,然后打出租车去比利时办事处。晚上,当时的办事处主任把我带到他的宿舍就离开了。宿舍转了一圈,发现没有吃的,只有我一个人在,也没有宿舍钥匙,更不清楚怎么去外面的超市买东西。搜遍宿舍只找到一包挂面,只好拿盐水煮了,没有筷子,只能用叉子。从那以后,每次出差,都要带两包方便面,以防万一。

第二天,一个本地客户经理带着去卢森堡交流。很可惜,讲完胶片之后,经常听不懂客户的问题。现在脑海中还能浮现出额头上渗着油汗,试图听懂客户问题,在白板上给客户解释的画面。带我来的客户经理,最后和我说,“抱歉,我也不懂中文,没有办法给你翻译”。

从比利时回荷兰还出了点问题。在布鲁塞尔做火车,买了票,但是不懂怎么坐车,到了售票员说的那个站台,有趟车停在那里就直接上车了。做了几站之后,发现不对,和来的时候怎么不一样,问了旁边的老外,发现坐错了。下车去问“information”,很可惜,他不会说英语。折腾半天,也不清楚怎么转车。无奈,拦住一个中国的女孩子问她,说我坐反了,她刚好也去布鲁塞尔中央车站转车,可以带我到中央站,并解释了详细的到阿姆转车的过程。本来两个小时的路程,花了五个小时,晚上10点多才到阿姆。记忆中好像还给我留了名片,是一个船务公司的,约了下次到比利时再联系。只是下次到比利时是几年之后的事情了,名片都丢了。

这次失败的出差经历,打击很大。回来之后就开始努力学习英语。其实也没有窍门,从网上下载了新概念英语2-4册,每天晚上听一小时、早上读一小时,努力把每篇文章背下来。可能持续了有小半年时间吧,其实在大学里面还是有点底子的,只是要解决听力的问题。

好在这次交流的客户后来选择了KPN用华为的产品建设他们的网络。



第一次失败的项目。

2008年初,荷兰一个光纤供应商EuroFiber发了个波分标书。据客户经理说,客户是主动找到华为的,好像客户经理还很自豪。那时候还没有投标办,所有的事情要自己搞定。应该是我自己笨,不懂得求助。我、本地客户经理、本地传输主管一起完成了所有工作(不是自豪,是鄙视自己)。我负责答标、网络设计报价、客户交流等。记忆中,从头到尾只是找地区部领导申请了一下价位,对竞争对手、项目运作、汇报求助等一无所知,客户来了问题和需求就马上响应,文档做的很好。

自然这是个失败的项目,客户最后选择了Infinera,Infinera至今在荷兰只有这一个项目。客户给的理由是:“感觉不到华为对他们的**,华为只关注大运营商”。客户经理给的理由是:“我们给客户宣讲时,总是讲我们突破了多少大T,所以给客户留下了这个印象”。觉得很委屈,因为宣讲都是我做的。后来代表处代表参加了客户的会议,问客户不选择华为的理由,客户说:“Infinera拿到这个项目,经常在他们办公室,对他们支持很到位;华为只是被动的响应他们的问题,很少去他们办公室”。记得当时自己终于如释重负----原因不在我这里。

到2012年完成了公司级的ETN-IP项目之后,回顾自己走过的路,才意识到当年自己做的多么失败,难辞其咎。没有项目分析会、竞争分析、商务分析、项目运作等等,可以说是一无所知。也难怪现在回忆以前做过的项目,每个项目都能回忆起来项目分析会、项目组挑灯夜战的场景,这个项目却没有任何感觉,“静悄悄的来,静悄悄的走,不带走一点关注,不留下一丝印象”。



2009年再尝败绩。

2009年初,KPN国际关口局项目发标。忘记为什么我这个光网的产品经理负责了IP的项目,可能是当时荷兰没有IP产品经理吧。我和市场技术的一个同事一起,负责答标工作,用NE5000E。这次项目,仍然没有留下深刻印象,算是学习了一下IP产品知识。项目丢给了思科。



现在想来,从2008年到2009年上半年,我完全没有从研发的思维转变过来,不懂销售项目的运作思路,只关注自己。不知道要关注客户、关注竞争对手,甚至也不明白要调动周边的资源:客户经理、系统部、地区部等。完全是被动的完成任务。

完成从研发到行销的转变,我花了两年的时间,到2010年才逐渐摸到行销的脉络,真够慢的!这和我内向的性格也有关,和其他人沟通太少,认识不到自己的问题。要感谢华为,如果不是有个大平台,允许失败,可能我早已经被扼杀在摇篮里,没有后面的机会了。

这也可能是新手的大忌,要快速的转变思维,牢记“你不是一个人在战斗,你不能一个人去战斗!”。记得曾经和当时部门的老外主管聊,我说缺竞争对手的信息和客户信息,他的直接反应是“我们只需要自己做到最好”。现在我知道了,“自己做到最好”只是个最基础的工作而已。



初尝胜利-荷兰VDF波分项目

2009年下半年,在负责VDF客户PTN和SDH产品交付工作期间,提前发现了现网老旧波分的改造机会(比竞争对手提前)。先后和客户进行了几场交流,包括设备、网管、网络设计、预算分析等。仍然是单打独斗,所有的技术交流都自己搞定。碰到客户的技术问题,首先想到的是自己去查手册,在资料中找答案,找不到才去求助研发。自己完全理解了,才去答复客户。现在还记得,曾经仔细的研读产品手册,列一个大表,把各种单板的性能、主要参数、替代关系都列出来;问过研发的问题,都作为经验记下来。所以在荷兰期间,部门一直拿我作为光网的半个专家。

仍然不懂得求助,但有了点项目运作的概念;交流中注意把对华为有利的技术需求引导给客户。而且只有华为和ALU在VDF集团的短名单里面,ALU还没有发现这个机会,客户一直在和华为交流。从解决方案上,华为当时比较强的关键技术点,ASON、40G、OTN、ROADM、VC和ODU统一交叉等,非常符合客户对现网Sycamore SDH设备改造的需求。项目成功之后,据客户说,ALU收到标书后,曾经和VDF抱怨说这个标书是为华为写的。在投标的关键时候,ALU的客户经理还去休假了,等到客户基本完成决策才回来。我们的前期介入,努力工作和对手的懈怠,是项目成功的一个关键因素。

2010年初的时候,项目已经胜利在望了,已经和客户安排了四月份去国内培训的计划,培训完就开始网络设计和建设。就在此时,VDF集团把爱尔兰、新西兰、荷兰三个波分项目捆绑在一起,“三兰”项目发标。尽管荷兰客户建设在即,要求直接选择华为开始建设,但还是没有阻挡住集团发标。

VDF系统部成立了三个国家组成的项目组,开始统一答标和谈判。集团答标期间,我按原计划在五一期间带客户回国培训。这时候已经由投标办负责答标工作了。

回国之前又出了个小问题,五个客户已经从阿姆飞香港去了深圳。冰岛火山爆发了,导致我要乘坐的航班取消。客户已经到了,而且在答标期间,我必须去啊。在阿姆机场等了很久,最后从赫尔辛基和吉隆坡两次转机,辗转超过24小时,赶上了星期一早上开始的客户培训。为期两周的时间,和这批客户建立了很好的信任关系,他们在评标之前对华为的产品和解决方案已经非常熟悉。

后来又去卢森堡一起参与了集团的谈判。自然,这个项目中标了。这个项目后来写了个案例----《N国V 子网波分项目拓展总结》,可以在3ms上找到。



2010年充分验证了“坚持就是胜利”。那个冬天是荷兰多年不遇的寒冬,圣诞节前一场大雪,导致全线交通瘫痪。我是当天的机票去成都休假,晚上8点多的飞机,下午五点多到火车站,正常情况下15分钟可以到机场。结果火车、公交、出租车全部停运,折腾一个多小时,还在火车站。最后请办公室的同事开车送我去机场,路上又堵车了一个多小时,8点还没有到机场,同事都劝我放弃了。不到黄河不死心啊,坚持到了机场。结果飞机晚点了两个小时,而且大部分乘客都没有赶到机场。这次有幸第一次升舱坐头等舱。

总之,2010年是幸运的一年,第一次作为主力拿项目,第一次做头等舱。



2011年运作成功公司级项目。

2011年初,和KPN交流过程中发现其城域网面临扩容的压力。当时我还是波分的产品经理,主推MS-OTN用ODU广播承载BTV业务。经过上半年多次交流沟通,摸清了现网的大部分信息,认识了IP部门的客户,帮助客户和华为研究MS-OTN标准的专家建立了联系。年中KPN发了承载网架构演进的FCN RFI(Fixed Core Network),由于我们在RFI期间的优异表现,华为和ALU进入下一轮。当年10月份发了BNG和城域的RFQ,我司一举中标。

不详细的介绍这个项目了。这是个公司级项目,运作过程比较复杂。先后总结了两个案例《厚积薄发,从无到有-N国K运营商FCN项目拓展总结》。后来和几位领导一起,完成了关于KPN项目运作思路的案例《从Bottom-up到Top-Down--从单域设备供应商到全面合作伙伴》。这两个案例都能在3ms上看到。

这个项目,投入了将近一年时间,直到圣诞节假期开始的前一天晚上七点,才结束和KPN的谈判,拿到了意向书。我是项目运作和解决方案责任人,用当时一位领导的话说,“你走路的时候都眉头紧锁,目不斜视”。确实投入了很大精力,收获也很大,包括物质的和精神的。

其实2011年初,我已经萌生了离开行销的念头。如果不是因为这个项目,早在三年前已经回研发或者离职了。感谢当年代表处和地区部主管(辛文、冯俊、谭春霞、曾兴云、王德贤等),给予了充分的信任和支持,允许我把所有的精力放在这一个项目上。在这个项目中才真正体会了项目运作、团队合作管理、竞争分析、项目策略、客户关系等等这些概念。个人状态也发生了很大改变,用当时代表辛文的话说,“经过了这个项目,能感觉到你的自信心和幽默感都改变了很多”。

的确如此,2011-2012年是我毕业之后变化最大的阶段,在项目中向这几位优秀的领导学了很多。2011年,我在电脑屏幕的下面贴了个字条“冷静”,提醒自己要心平气和的做事。2012年,开始有了改变,我老婆都感觉到了,说我比以前多了点包容,愿意听别人说话了。

但是说话做事还是很直接,用部门一个本地同事的话说,我的口头禅是“Let’s make a summary”。每次组织会议,听了与会人发表意见后,我总是忍不住要打断漫无边际的讨论,要马上列出行动计划去分配执行。确实性格很难改变,也许永远做不到人情练达。只求以诚待人,简单快乐。用我老婆的话说:“丑也有丑的好处,那张脸一眼看上去至少很真诚;而且心思简单,躺下10分钟就能开始打呼噜”。



2012年带团队做项目。

2012年有幸获得公司金牌产品经理,第一次拿到那么大一个铜牌。开始做部门主管,说是主管,也就十来个人。

2012年带部门两个产品经理(一中一外的配置)一起做电源替换项目。用两个字总结就是“忽悠”:“忽悠”产品经理去做客户关系;“忽悠”服务同事降价;“忽悠”产品线的人来一线做解决方案;“忽悠”地区部给商务;“忽悠”客户同意我们的商务。“忽悠”的还比较成功,这个项目没有发标直接给了华为,后面的交付也比较成功。

2012年6月参加了FLM培训(First Line Manager),自己看了些书,包括德鲁克的几本经典著作、心理学方面的入门知识等;期望自己能做一个好的管理者。仔细想,也实在总结不出来经验。2012年,和一个产品线领导吃饭,他就问了我这个问题。忘记怎么回答了,好像是脱口而出:“开放的心态,以诚待人;多看他人的优点,承担责任,帮助他人成功”。



后记

看到“后记”这两个字,也许你要失望了,没有讲啥“一招制敌”的功夫吗!是的,我还没有练成。我只想把我走过的弯路实事求是的告诉刚入门的产品经理,希望我们不要在同一个坑里花太多时间才能爬出来。

揭开伤疤,解剖自己真心不容易!

作者: compare2000    时间: 2014-07-30 17:06
说说wakelock[未结束]  wake_lock_init(struct wakelock *lock, int type, const char *name);


void wake_lock(struct wake_lock *lock);



void wake_lock_timeout(struct wake_lock *lock, long timeout);



void wake_unlock(struct wake_lock *lock);




在/proc/wakelocks中可以查看当前的wakelock.

WAKE_LOCK_SUSPEND 阻止系统进入suspending状态(睡眠状态)。
WAKE_LOCK_IDLE 阻止系统进入low-power idle状态,该状态可能会增加响应时间。(IDLE,要CPU支持,当CPU不支持IDLE时,该选项不起作用)




用户空间通过向

/sys/power/wake_lock

/sys/power/wake_unlock

来上锁和解锁。







》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》

插播一点电源管理:

kernel/power/suspend.c中



c代码
30 const char *const pm_states[PM_SUSPEND_MAX] = {
31 #ifdef CONFIG_EARLYSUSPEND
32         [PM_SUSPEND_ON]         = "on",
33 #endif
34         [PM_SUSPEND_STANDBY]    = "standby",
35         [PM_SUSPEND_MEM]        = "mem",
36 };




手机有这三种状态。

suspend_on,手机正常工作状态

standby,手机休眠状态

mem,手机在休眠态,但数据还在内存中,中断可**。








early_suspend, later_resume,当用户空间请求suspend时,比如按下power键,系统会进入early_suspend,这时,所有early_suspend注册的函数都会逐一调用。当所有的wakelock释放后,系统就会真正休眠。
i2c 如何根据中断号找设备


1, cat /proc/interrupts

76:       1450       GIC  i2c_designware
77:       9089       GIC  i2c_designware
78:       4976       GIC  i2c_designware
79:       2708       GIC  i2c_designware

这里76, 77, 78, 79分别对应哪个设备

2,在注册的地方找代码:
irq = platform_get_irq(pdev, 0);
request_irq(irq, i2c_dw_isr, IRQF_DISABLED | IRQF_NO_THREAD, pdev->name, dev);

int platform_get_irq(struct platform_device *dev, unsigned int num)
{               
         struct resource *r = platform_get_resource(dev, IORESOURCE_IRQ, num);
         
         return r ? r->start : -ENXIO;
}      
EXPORT_SYMBOL_GPL(platform_get_irq);

这里,是通过设备定义的resouce来查找irq.


在:arch/arm/mach-hi6620/dev_i2c.c
126 static struct resource i2c_resource ## chip_id[] = {\
127         [0] = {\
128                 .start = REG_BASE_I2C ## chip_id,\
129                 .end = REG_BASE_I2C ## chip_id + REG_I2C ## chip_id ## _IOSIZE - 1,\
130                 .flags = IORESOURCE_MEM,\
131         },\
132         [1] = {\
133                 .start = IRQ_I2C ## chip_id,\
134                 .end = IRQ_I2C ## chip_id,\
135                 .flags = IORESOURCE_IRQ,\
136         },\
137 };\


也就是说,i2c_designware的irq值是start = IRQ_I2C ## chip_id

在/hisi/platform/hi6620cs_asic/soc_irqs.h

#define IRQ_GIC_START       32

#define IRQ_I2C0        (IRQ_GIC_START + 44)
#define IRQ_I2C1        (IRQ_GIC_START + 45)
#define IRQ_I2C2        (IRQ_GIC_START + 46)
#define IRQ_I2C3        (IRQ_GIC_START + 47)
#define IRQ_I2C4        (IRQ_GIC_START + 4
#define IRQ_I2C5        (IRQ_GIC_START + 49)


通过这里,可以看到这里76, 77, 78, 79分别对应I2C.0~5.



同时,可以在/hisi/platform/hi6620cs_asic/soc_irqs.h,查到其他任意irq是怎么算出来的
查看中断的触发类型,中断是否在执行,是否cpu绑定,是否为wakeup



比如在中断上半部查询当前irq的触发状态,就可以使用struct irq_data::state_use_accessors来查询。在include/linux/irq.h中定义了irqd_set_trigger_type,不过是inline static,在自己的模块无法使用,可以把对应函数代码移动到自己模块使用。



同理,还有其他几种状态,都可以通过state_use_accessors来获取。

中断和中断处理程序的一点总结  今天简单的学习了一下进程调度的东西,总结在下面。

下面代码为内核2.6.34版本。

1,中断分为上半部和下半部
   上半部:中断处理程序,接收到一个中断,立即开始执行,但只做有严格时限的工作,这些工作要求所有中断被禁止。
   下半部:可以稍后完成的工作在下半部完成。
2,中断处理程序注册函数
      request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
     const char *name, void *dev)
   irq是分配的中断号,要么提前定死,要么动态获得。
   handler指向处理这个中断的实际中断处理程序。
   flags 可以为0,也可以是下面一个或者多个标志的位掩码
       /*
         * These flags used only by the kernel as part of the
         * irq handling routines.
         *
         * IRQF_DISABLED - keep irqs disabled when calling the action handler
         * IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
         * IRQF_SHARED - allow sharing the irq among several devices
         * IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
         * IRQF_TIMER - Flag to mark this interrupt as timer interrupt
         * IRQF_PERCPU - Interrupt is per cpu
         * IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
         * IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
         *                registered first in an shared interrupt is considered for
         *                performance reasons)
         * IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
         *                Used by threaded interrupts which need to keep the
         *                irq line disabled until the threaded handler has been run.
         * IRQF_NO_SUSPEND - Do not disable this IRQ during suspend
         *
         */
    name是与中断相关的设备的ASCII文本表示法。
    dev主要用于共享中断线,如果无需共享,则赋值NULL,否则必须传递唯一的信息。
3,request_irq返回0为成功,返回非0为失败。最常见的是返回-EBUSY,表示给定的中断线已经在使用。
4,request_irq可能会睡眠,因此不能再中断上下文或其他不允许阻塞的代码中调用该函数。之所有会睡眠,是因为在注册过程中会调用kmalloc()来请求分配内存,而该函数是可以睡眠的。
5,释放中断处理程序函数为free_irq(unsigned int irq, void *dev_id),如果指定的中断线是非共享的,则同时禁用该中断线。
6,典型的中断处理程序声明如下:
    irqreturn_t amd_iommu_int_handler(int irq, void *data)
    这里要注意该函数的返回值是一个特殊类型irqreturn_t。
    /**
     * enum irqreturn
     * @IRQ_NONE  interrupt was not from this device
     * @IRQ_HANDLED  interrupt was handled by this device
     * @IRQ_WAKE_THREAD handler requests to wake the handler thread
     */
7,中断处理程序无需重入。当一个给定的中断处理程序正在执行时,相应的中断线在所有处理器上都会被屏蔽,以防止在同一个中断线上接受另一个新的中断。
   但是不同中断线上的其他中断都可以处理。
   
8,共享中断处理程序注册函数的参数flags必须设置IRQF_SHARED标志,而且dev参数必须唯一真实的设备id,否则无法区分是哪个设备发来的中断。
   所有共享中断线的驱动程序都必须满足这些要求。而且注册共享中断处理程序时必须满足:要么该中断线还没有被注册,要么该中断线上的所有已注册的处理程序都设置了IRQF_SHARED标志。
   
9,当执行一个中断处理程序或下半部时,内核处于中断上下文中。中断上下文和进程没有关系,所以中断上下文不可以睡眠,而且不能从中断上下文中调用某些可以睡眠函数。
10,中断栈:中断处理程序早期是没有自己的栈,而是共享所中断进程的内核栈。2.6后,中断处理程序有了自己的栈,每个处理器一个,大小为一页。
11,中断处理机制:硬件设备产生中断,通过总线把电信号发送给中断控制器,如果中断线此时没有被屏蔽,处于激活态,则会把中断发往处理器。
    如果处理器上没有禁止该中断,则立即停止正在做的事,关闭中断系统,跳到中断处理程序的入口点。在内核中,中断处理程序都有一个入口点,这样内核就
    可以知道中断的IRQ号,入口点会在栈中保存IRQ号,并存放当前寄存器的值(这些值属于被中断的任务),然后内核调用函数unsigned int __irq_entry do_IRQ(struct pt_regs *regs)。
    struct pt_regs {
     long ebx;
     long ecx;
     long edx;
     long esi;
     long edi;
     long ebp;
     long eax;
     int  xds;
     int  xes;
     int  xfs;
     int  xgs;
     long orig_eax;
     long eip;
     int  xcs;
     long eflags;
     long esp;
     int  xss;
    };
    do_IRQ会调用handle_irq运行中断处理程序,然后调用irq_exit处理软中断(如果必须且能处理的话,这个软中断是softirqs,下半部实现的一种机制,不同于软件中断),最后将原来寄存器的值恢复。
    然后从入口点跳到ret_from_intr(汇编写的/arch/x86/kernel/entry.s),它会检查重新调度是否正在挂起,如果正在挂起,则分别按照用户抢占和内核抢占处理(具体可参考进程调度http://hi3ms.huawei.com/group/2642/blog_48236.html?uid=9986)。
    ret_from_intr:
     DISABLE_INTERRUPTS(CLBR_NONE)
     TRACE_IRQS_OFF
     decl PER_CPU_VAR(irq_count)
     leaveq
     CFI_DEF_CFA_REGISTER rsp
     CFI_ADJUST_CFA_OFFSET -8
   
12,/proc/interrupts文件中存放的是系统中与中断相关的统计信息。
    第一列是中断线,第二列是接受中断数目的计数器,第三列是处理该中断的中断控制器,最后一列是与这个中断相关的设备名字。
    root@localhost:/proc> vi interrupts

           CPU0       CPU1       CPU2       CPU3       CPU4       CPU5       CPU6       CPU7
  0:         68          0          0          0          0          0          0          0   IO-APIC-edge      timer
  4:          0          0          0        210          0          0          0          0   IO-APIC-edge      serial
  9:          0          0          0          0          0          0          0          0   IO-APIC-fasteoi   acpi
14:          0          0          0       9545          0          0          0          0   IO-APIC-edge      ata_piix
15:          0          0          0          0          0          0          0          0   IO-APIC-edge      ata_piix
16:          0          0    1224941          0          0          0          0          0   IO-APIC-fasteoi   ehci_hcd:usb1, eth0
, eth1
17:          0          0          0          0          0          0          0          0   IO-APIC-fasteoi   uhci_hcd:usb2
18:          0          0          0          0          0          0          0          0   IO-APIC-fasteoi   uhci_hcd:usb3
19:          0          0          0          0          0          0          0          0   IO-APIC-fasteoi   ata_piix
NMI:          0          0          0          0          0          0          0          0   Non-maskable interrupts
LOC:  448397250   81103735   16591016   16421416   16446300   16507987   16086415   16106542   Local timer interrupts
SPU:          0          0          0          0          0          0          0          0   Spurious interrupts
PMI:          0          0          0          0          0          0          0          0   Performance monitoring interrupts
PND:          0          0          0          0          0          0          0          0   Performance pending work
RES:       3836       1491         80         89         53         30         27         20   Rescheduling interrupts
CAL:         30         80         83         67         80         80         81         80   Function call interrupts
TLB:        932       1370       1588       1619       1723       1167         72         47   TLB shootdowns
ERR:          0
MIS:          0

13,一些中断控制函数或者宏
   local_irq_enable  --激活本地中断传递
   local_irq_disable  --禁止本地中断传递
   local_irq_save  --保存本地中断传递的状态,然后禁止本地中断传递
   local_irq_restore  --恢复本地中断传递到指定的状态
   disable_irq  --disable an irq and wait for completion
   disable_irq_nosync  --disable an irq without waiting
   enable_irq  --enable handling of an irq
   irqs_disabled  --return flags == RAW_IRQ_DISABLED;
   in_interrupt  --是否在中断上下文中
   in_irq --是否正在执行中断处理程序
   in_softirq  --是否正在执行软中断程序


作者: compare2000    时间: 2014-07-30 17:08
如何在android上编译单个modules  在调试阶段,如果仅仅是单个.ko的反复修改和调试,可以通过单独编译modules的方法进行。



1,增加一个makefile



bash代码
obj-m += xxxx.o

KERNEL_PATH=../../../../../out/target/product/xxxx/obj/KERNEL_OBJ

all:
    make -C $(KERNEL_PATH) M=`pwd` ARCH=arm CROSS_COMPILE=arm-linux-androideabi- modules

clean:
    make -C $(KERNEL_PATH) M=`pwd` ARCH=arm CROSS_COMPILE=arm-linux-androideabi- clean


2, 增加对应的xxxx.o的源文件



3, make CFLAGS_MODULE=-fno-pic



4, push 该模块到手机上


5, insmod

【内核翻译】sysfs,附参考代码  sysfs  The filesystem for exporting kernel objects
Documentation/filesystems/sysfs.txt


Patrick Mochel  <mochel@osdl.org>
Mike Murphy <mamurph@cs.clemson.edu>
Revised:    16 August 2011
Original:   10 January 2003


Translator: Bill Wang(Liang) (w00173980)


What it is:
sysfs是一个ram-based filesystem,基于ramfs。用于导出kernel的数据结构,属性以及kernel space和user space之间的联系。


sysfs和kobject紧密相关。请查阅Documentation/kobject.txt来了解kobject的信息。


Using sysfs
sysfs在CONFIG_SYSFS选项打开后,编译到内核。通过mount来挂载:
        mount -t sysfs sysfs /sys


Directory Creation
每一个注册到系统中的kobject都会有一个sysfs的目录。这个目录是对应Koject的父目录的子目录。【译注:这句话有点绕,有点递归的意思,意思是说,每个kobject都有一个目录,如果kobject有一个parent,那么他的目录就是parent对应的目录的子目录,如果么没有parent,那么就是/sys的子目录】


通过目录结构来标示kobject的层次关系。


Sysfs内部持有一个指针,该指针指向一个在sysfs_dirent对象关联的目录中实现目录的kobject【?】。在过去,Kobject pointer被sysfs用来在kobject 打开或者关闭的时候做引用计数。在现在的sysfs实现中,kobject的引用计数仅仅通过sysfs_schedule_callback()来修改。


Attributes
kobject的属性以文件的形式导出到文件系统中。sysfs将文件I/O操作转换为kobject属性中定义的方法。


属性应该是ASCII文件,建议每个文件只有一个值。不过每个文件只有一个值,效率不是很好,所以也可以有一组相同类型的值。


混合类型,多组数据,或者很复杂的格式化数据不是很恰当。


一个属性对的定义很简单:
        struct attribute {
                char                    * name;
                struct module       *owner;
                umode_t               mode;
                };

        int sysfs_create_file(struct kobject * kobj, const struct attribute * attr);
        void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr);


单单一个属性不能够对属性进行读或者写。各模块需要定义自己的attribute结构,添加对应的读写方法。


比如设备模型使用的strcut device_attribute:
        struct device_attribute {
                struct attribute attr;
                ssize_t (*show)(struct device *dev, struct device_attribute *arr, char *buf);
                ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);


同时也定义了宏来简化定义:
        #define DEVICE_ATTR(_name, _mode, _show, _store)\
        struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)


这样声明一个属性就很简单:
        static DEVICE_ATTR(foo, S_IWUSR | S_IRUGO, show_foo, store_foo);
这句话等同于:
        static struct device_attribute dev_attr_foo = {
                .attr = {
                        .name = "foo",
                        .mode = S_IWUSR | S_IRUGO,
                },
                .show = show_foo,
                .store = store_foo,
        };


Subsystem-Specific Callbacks
当一个模块定义一个新的属性类型,那么就必须实现对应的sysfs操作,这些操作用来读写属性文件。
        struct sysfs_ops {
                ssize_t (*show)(struct kobject*, struct attribute *, char*);
                ssize_t (*store)(struct kobject*, struct attribute *, const char *, size_t);
        }
[子系统应该已经定义了struct kobj_type,这kobj_type中包含了sysfs_ops,参考kobject的文档来查看更多细节]


当一个文件被读写,sysfs就会调用合适的方法。该方法会向通用的struct kobject 和 struct attribute pointers转换为特性的指针类型,然后调用关联的方法。


例子:
drivers/base/core.c


        #define to_dev(obj) container_of(obj, struct device, kobj)
        【译注:返回obj所在的struct device的指针】
        #define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr)
        【译注:返回attr所在的struct device_attribute的指针】


        static ssize_t dev_attr_show(struct kobject *kobj, struct attribute* attr, char *buf)
        {
                struct device_attribute *dev_attr = to_dev_attr(attr);
                struct device *dev = to_dev(kobj);
                ssize_t ret = -EIO;


                if (dev_attr->show)
                        ret = dev_attr->show(dev, dev_attr, buf);
                if (ret >= (ssize_t)PAGE_SIZE) {
                        print_symbol("dev_attr_show: %s return bad count\n", (unsigned long)dev_attr->show);
                }
                return ret;
        }


【?我的例子里,没有注册设备,只是注册是kobject,为何也能找到对应的show, store,是不是kobject只能用于设备模型,而不能用于其他方面】


Reading/Writing Attribute Data
为了读写属性,show()和store()方法必须在声明属性的时候定义。
        ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf);
        ssize_t (*store)(struct device *dev, struct device_attribute *att, const char *buf, size_t count);
换句话说,这两个方法参数只能有一个对象,一个属性和一个buf。


sysfs申请PAGE_SIZE大小的buffer,然后把这个buf传递给方法。每次读写,sysfs只会调用一次方法。这样在方法实现上就有这些特点:
        ---对于read(2),show应该填满整个buffer。由于一个属性只能export一个值,或者一组同类型的值,所以buffer不会太大。
        这个办法也可以让用户空间进行部分读和seek操作。如果用户空间通过seek到0,或者通过pread(2)时用参数0,那么show()多调用一次。
        ---对于write(2),sysfs会将写buffer传入到store()。
        当写sysfs文件时,用户空间应该先读取文件的值,修改成期望的值,然后把整个buf写回。


        属性的读写操作应该操作同一个buf。
        【???】为何写入writehere时,store()方法调用了两次? echo writehere>/sys/hwobj/write_node
其他需要注意的:
        ---【???】写操作会引起show()的位置回到文件开始。
        ---buffer的大小总是PAGE_SIZE。
        ---show()应该返回写入到buf中的长度,返回值和scnprintf()一样。
        【译注】
        scnprintf -- Format a string and place it in a buffer
        int scnprintf(char* buf, size_t size, const char *fmt, ....);
        buf: the buffer to place the result into
        size: the buffer size, including the trailing null space
        【end】
        ---store()应该返回buffer所用的大小。如果所有的buffer都用完了,只需要返回count参数?
        ---show()或者store()方法可以返回错误。如果一个错误的参数传入,那么可以返回一个错误。
        ---show()或者store()传入的对象,会在sysfs引用的object的内存中(?)。所以,物理的设备可能不会存在,所以,如果需要,要检查物理设备是否存在。


简单的实现可以这样:
        static ssize_t show_name(struct device *dev, struct device_attribute *attr, char *buf)
        {
                return scnprintf(buf, PAGE_SIZE, "%s\n", dev->name);
        }
       
        static ssize_t store_name(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
        {
                snprintf(dev->name, sizeof(dev->name, "%.*s", (int)min(count, sizeof(dev->name) - 1), buf);
                return count;
        }


static DEVICE_ATTR(name, S_IRUGO, show_name, store_name);


(注意:真实的应用中,不应该通过userspace来设置device的名字。


Top Level Directory Layout
sysfs的目录结构导出了kernel的数据结构关系。
顶层的sysfs目录结构类似于:
        block/
        bus/
        class/
        dev/
        devices/
        firmware/
        net/
        fs/
devices/用一个文件系统标示了设备树。直接映射了内部的内核设备树,也就是struct device的结构。


bus/用罗列了内核中各种bus类型。每一种bus的目录中的都有两个文件夹:
        devices/
        drivers/


/sys/bus/[bus-type]/devices/ 包含了在系统中发现的设备的链接,指向了/sys/devices下的设备。
/sys/bus/[bus-type]/drivers/ 包含了bus上挂在的设备的驱动。


fs/包含了当前系统的filesystem,子目录时名子文件系统想要导出的属性。


dev/包含了两个文件夹,char/和block/。在各自的目录中,分别时用<major>:<minor>命名的符号链接。


更多关于设备模型的特性可以在/Documentation/driver-model/查看。


TODO: Finish this section


Current Interfaces
- devices (include/linux/device.h)
----------------------------------
Structure:

struct device_attribute {
         struct attribute        attr;
         ssize_t (*show)(struct device *dev, struct device_attribute *attr,
                         char *buf);
         ssize_t (*store)(struct device *dev, struct device_attribute *attr,
                          const char *buf, size_t count);
};

Declaring:

DEVICE_ATTR(_name, _mode, _show, _store);

Creation/Removal:

int device_create_file(struct device *dev, const struct device_attribute * a    ttr);
void device_remove_file(struct device *dev, const struct device_attribute *     attr);


- bus drivers (include/linux/device.h)
--------------------------------------
Structure:

struct bus_attribute {
         struct attribute        attr;
         ssize_t (*show)(struct bus_type *, char * buf);
         ssize_t (*store)(struct bus_type *, const char * buf, size_t count);
};

Declaring:

BUS_ATTR(_name, _mode, _show, _store)

Creation/Removal:

int bus_create_file(struct bus_type *, struct bus_attribute *);
void bus_remove_file(struct bus_type *, struct bus_attribute *);


- device drivers (include/linux/device.h)
-----------------------------------------

Structure:

struct driver_attribute {
         struct attribute        attr;
         struct attribute        attr;
         ssize_t (*show)(struct device_driver *, char * buf);
         ssize_t (*store)(struct device_driver *, const char * buf,
                          size_t count);
};

Declaring:

DRIVER_ATTR(_name, _mode, _show, _store)

Creation/Removal:

int driver_create_file(struct device_driver *, const struct driver_attribute     *);
void driver_remove_file(struct device_driver *, const struct driver_attribut    e *);


Documentation
~~~~~~~~~~~~~
sysfs目录结构和属性定义了kernel和userspace之间的ABI。因此上,这些ABI要稳定和恰当的说明。所有新的sysfs attribute要在Documentation/ABI中说明,查看Docomentation/ABI/README来了解更多。
作者: compare2000    时间: 2014-07-30 17:10
本帖最后由 compare2000 于 2014-09-12 16:38 编辑

samp_sysfs.c.txt
/*
* sysfs.example.c - sample code for Documentation/filesystems/sysfs.txt
*
* Copyright (C) 2013  Bill Wang(Liang)
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA.
*/


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/kobject.h>
#include <linux/device.h>

static struct kobject *hwobj=NULL;

ssize_t show_node(struct device *dev, struct device_attribute *att, char *buf)
{
        pr_info("%s called\n",__func__);
        return scnprintf(buf, sizeof("show_node", "%s", "show_node";
}

ssize_t store_node(struct device *dev, struct device_attribute *att, const char *buf,
                size_t count)
{
        pr_info("%s store value %s\n",__func__, buf);
        return sizeof(buf);
}


static DEVICE_ATTR(write_node, S_IWUSR | S_IRUGO, NULL, store_node);
static DEVICE_ATTR(read_node, S_IWUSR | S_IRUGO, show_node, NULL);

static int __init samp_sysfs_init(void)
{
        int ret = 0;
        pr_info("%s module init\n", __func__);
        hwobj = kobject_create_and_add("hwobj", NULL);
        if (IS_ERR(hwobj)){
                ret = PTR_ERR(hwobj);
                pr_err("object alloc failed\n";
                goto exit_del_kobject;
        }

        ret = sysfs_create_file(hwobj, &dev_attr_read_node.attr);
        if (ret){
                pr_err("create read node failed %d\n", ret);
                goto exit_del_sysfs_read;
        }

        ret = sysfs_create_file(hwobj, &dev_attr_write_node.attr);
        if (ret){
                pr_err("create write node failed %d\n", ret);
                goto exit_del_sysfs_write_node;
        }
        return ret;

exit_del_sysfs_write_node:
        sysfs_remove_file(hwobj, &dev_attr_write_node.attr);
exit_del_sysfs_read:
        sysfs_remove_file(hwobj, &dev_attr_read_node.attr);
exit_del_kobject:
        kobject_del(hwobj);
        pr_info("%s return %d \n", __func__, ret);

        return ret;
}
static void __exit samp_sysfs_cleanup(void)
{
        sysfs_remove_file(hwobj, &dev_attr_write_node.attr);
        sysfs_remove_file(hwobj, &dev_attr_read_node.attr);
        kobject_del(hwobj);
        printk(KERN_INFO "hell_cleanup\n";
}

module_init(samp_sysfs_init);
module_exit(samp_sysfs_cleanup);

MODULE_AUTHOR("Bill Wang(Liang)";
MODULE_DESCRIPTION("sample code for Documentation/filesystems/sysfs.txt";
MODULE_LICENSE("GPL";

external folder中linux kernel header的作用






在Linux中,有一个external/kernel-headers/original,这个文件夹中有很多linux的头文件,与kernel/include/linux有很多重复的文件。


1、Android external folder


External refers to external open source libraries. That means libraries that the Android platform depend upon but that are not primarily developed and maintained by the Android open source project. Typical examples are webkit for the browser, FreeType for fonts, SqlLite for databases and so on. As more features are added to Android, more of these libraries are included in external.


external folder是外部的open source lib,这些lib是android依赖的,但是主要的开发和维护又不是android来做


http://stackoverflow.com/questio ... older-functionality


2,external/kernel-headers/original
(external/kernel-headers/original/README.TXT)
这个文件夹里的头文件是Bionic来用生成一个干净的user-land(user space)头文件。基于GPL2+。




3、Bionic




Bionic libc是BSD standard C library发展而来,最早是Google为Android开发。作用主要有三个:
a,BSD license: 通过BSD license,Android程序就和GPL分隔开来。
b,Bionic比GNU C Library要小的多。
c,Bionic的速度更快。


Bionic也缺少很多完整的libc的特性,比如宽字符和C++的异常处理。一些函数也没有实现。


(http://discuz-android.blogspot.c ... ve-libc-bionic.html)




HAL层,以及除了kernel意外的任意用到的C头文件,都是bionic中的。
./core/pathmap.mk,pathmap_INCL :=    libc:bionic/libc/include
http://www.linuxidc.com/Linux/2011-03/33672.htm )


Android编译环境所用的交叉编译工具链是prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/arm-eabi-gcc,-I和-L参数指定了所用的C库头文件和动态库文件路径分别是bionic/libc/include和out/target/product/generic/obj/lib
(http://www.360doc.com/content/09/0624/17/36491_4018938.shtml


kernel中用的头文件在kernel/include中。user space用的头文件在bionic下。


4,Bionic kernel header
(bionic/libc/kernel/README.TXT)
Bionic有一套干净的Linux头文件,这些头文件是通过bionic/libc/kernel/tools解析一份原始的,没有修改过的linux头文件生成的,这份头文件就在external/kernel-headers/original。
干净的头文件只包括类型和宏定义,不过处于效率的考虑,不会生成一些静态内联函数。
这些生成的头文件可以用于编译C++,C,也能够按照ANSI模式编译(linux kernel中用到了大量GNU扩展)。
生成头文件的过程是:
*external/kernel-headers/original
包含通常linux kernel source tree中的include目录。这里仅仅只应该包含android依赖的kernel头文件。


*bionic/libc/kernel/common
包含平台无关的头文件。
TMCC  http://www.broadcast.hc360.com/network/2001-6/35.htm





传输与复用结构控制信号(TMCC)应当包含以下信息:
    ● 用于每一狭槽的调制码组合;
    ● 用于每一狭槽的MPEG-TS识别码;
    ● 其它(例如顺序改变,广播报警、突发事件的旗标比特等)。
    TMCC信号应当比主信号优先传送,因为在没有TMCC信号时,主信号不能解调。TMCC信号恢复的最小间隙应当是一个超级帧的周期时间。接收机应当首先解码每一超级帧的TMCC信号。除了上述信息之外,TMCC信号还应当传送时间基准信号。

repo 备忘  1, repo branch/branches

这两个命令结果和目的一样。显示当前的branch状态和数量。

输出格式:


*P nocolor                   | in repo



输出格式有4列,第一列*显示了当前在用的branch.

第二列,可能是空格,p或者P,取决于upload的状态。

空表示还没有提交发布。

P表示所有提交已经通过repo upload发布。

p表示只有一部分通过repo upload发布。



2,repo diff
查看diff


3,  repo status [<project>...]


比较 工作目录和index,以及HEAD的区别。
结果显示三列,第一列表示index和HEAD的区别,大写。
-表示没有区别。
A:  added         (not in HEAD,     in index                     )
M:  modified      (    in HEAD,     in index, different content  )
D:  deleted       (    in HEAD, not in index                     )
R:  renamed       (not in HEAD,     in index, path changed       )
C:  copied        (not in HEAD,     in index, copied from another)
T:  mode changed  (    in HEAD,     in index, same content       )
U:  unmerged; conflict resolution required


第二列表示working tree和index的区别,小写。
-:  new / unknown (not in index,     in work tree                )
m:  modified      (    in index,     in work tree, modified      )
d:  deleted       (    in index, not in work tree                )




4, repo list
List all projects




5, repo start


Start a new branch for development


Usage: repo start <newbranchname> [--all | <project>...]


'repo start' begins a new branch of development, starting from the
revision specified in the manifest.


一个目录下有一大堆同质或不同质的程序,要测试系统处理并发任务的效率不得不写一个任务分发器(work load manager),用C写对新手来说还是有点大条的,利用make -j可以模拟一个简单的wlm。代码大概如下:
01.binaries := $(foreach ccode, \

02.              $(shell for cfile in `ls *.c`; do echo $cfile; done),\

03.              $(ccode:.c=))

04.$(binaries): %:%.c

05.        @echo BLD        $@

06.        @gcc -o $@       $< >/dev/null 2>&1

07.

08.to_run := $(foreach ccode, \

09.            $(shell for cfile in `ls *.c`; do echo $cfile; done),\

10.            to_run_$(ccode:.c=))

11.

12.run: clean $(to_run)

13.to_run_%: %

14.        @to_run=$@;                         \

15.         echo ${to_run##to_run_};        

16.

17.clean:

18.        @echo -n "-------------------------------- CLEAN "

19.        @echo "--------------------------------"

20.        @echo RMV        $(binaries)

21.        @rm -f                 $(binaries)

22.

23.

24..PHONY: run clean
复制代码运行 make -jN 即可
简而言之就是:
1. default 的 target 是 run,run 依赖于 clean 和 to_run
01.run: clean $(to_run)
复制代码2. to_run 是由一段 shell 产生的
01.to_run := $(foreach ccode, \

02.             $(shell for cfile in `ls *.c`; do echo $cfile; done),\

03.             to_run_$(ccode:.c=))
复制代码3. 假设目录下有 1.c 2.c 3.c 4.c ... 100.c 这100个c程序, 那段 shell 生成的变量 to_run 等于这样的一串: to_run_1 to_run2 ... to_run_100
4. to_run_%: % 那一段 rule 的意思是对于某个目标 to_run_73 依赖于 73,当 73 不存在或者 73.c 被更新过, make 就按照上面声明的 binaries 的规则去(重新)生成它:
01.$(binaries): %:%.c

02.         @echo BLD        $@

03.         @gcc -o $@       $< >/dev/null 2>&1
复制代码5.  73 生成以后就运行它,示例里用的是 echo
01.@to_run=$@;                         \

02.          echo ${to_run##to_run_};   
复制代码实际上可以是
01.@to_run=$@;                         \

02.          ./${to_run##to_run_};   
复制代码这样 73 就在适当的时候被运行了。
假设输入 make -j32 run,结果就是可执行文件 1, 2, 3 ... 32 同时被运行,其中的某个结束后 make 会自动启动后面的 33, 34 ... 直至所有的运行结束,这就模拟了一个简单的本地 work load manager。

这个make脚本的另外一个变种可以是:你有一个程序(假设叫 EXEC0),在一台16核的机器上你希望并行地运行32个带不同参数的 EXEC0 实例,总共运行 1024 个实例来测试系统或者 EXEC0 的运行效率,Makefile大概可以这样写:
01.ifeq ($(MAX),)

02.        MAX=1024

03.endif

04.

05.parameter0=0

06.parameter1=11

07.parameter2=22

08.parameter3=33

09.

10.prerequisites := $(foreach num,                                        \

11.                   $(shell i=0; while [ $$i -lt $(MAX) ];        \

12.                                do                                \

13.                                        echo $$i;                \

14.                                        i=$$((i + 1));                \

15.                                    done),                                \

16.                  target_$(num))

17.run(prerequisites)

18.

19.EXEC0: EXEC0.c

20.        gcc -o $@ $<

21.

22.target_%:EXEC0

23.        @./EXEC0 $(parameter$(shell expr $(subst target_,,$@) % 4))

24.

25..PHONY: run
复制代码一个无聊的EXEC0.c 可以是:
01.int

02.main(int argc, char **argv)

03.{

04.        sleep(atoi(argv[1]));

05.        printf("%s %d\n", argv[0], atoi(argv[1]));

06.

07.        return 0;

08.}
复制代码然后make -j32 MAX=1024
作者: compare2000    时间: 2014-09-12 16:38
本帖最后由 compare2000 于 2014-09-12 16:39 编辑

http://bbs.chinaunix.net/thread-1512640-1-1.html
http://blog.chinaunix.net/u/22754/showart_372628.html
=== 1 概述
     === 2 角色分工
     === 3 内核编译文件
        --- 3.1 目标定义
        --- 3.2 内嵌对象 - obj-y
        --- 3.3 可加载模块 - obj-m
        --- 3.4 导出符号
        --- 3.5 库文件 - lib-y
        --- 3.6 目录递归
        --- 3.7 编译标记
        --- 3.8 命令依赖
        --- 3.9 依赖关系
        --- 3.10 特殊规则
     === 4 辅助程序
        --- 4.1 简单辅助程序
        --- 4.2 组合辅助程序
        --- 4.3 定义共享库
        --- 4.4 C++语言使用方法
        --- 4.5 辅助程序编译控制选项
        --- 4.6 何时建立辅助程序
        --- 4.7 使用hostprogs-$(CONFIG_FOO)
     === 5 编译清除机制
     === 6 体系Makefile文件
        --- 6.1 变量设置
        --- 6.2 增加预设置项
        --- 6.3 目录表
        --- 6.4 引导映像
        --- 6.5 编译非内核目标
        --- 6.6 编译引导映像命令
        --- 6.7 定制编译命令
        --- 6.8 预处理连接脚本
        --- 6.9 $(CC)支持功能
     === 7 Kbuild变量
     === 8 Makefile语言
     === 9 Credits
     === 10 TODO
=== 1 概述
Makefile包括五部分:
     Makefile            顶层Makefile文件
     .config                  内核配置文件
     arch/$(ARCH)/Makefile      机器体系Makefile文件
     scripts/Makefile.*      所有内核Makefiles共用规则
     kbuild Makefiles      其它makefile文件
通过内核配置操作产生.config文件,顶层Makefile文件读取该文件的配置。顶层Makefile文件负责产生两个主要的程序:vmlinux (内核image)和模块。顶层Makefile文件根据内核配置,通过递归编译内核代码树子目录建立这两个文件。顶层Makefile文件文本一个名为arch/$(ARCH)/Makefile的机器体系makefile文件。机器体系Makefile文件为顶层makefile文件提供与机器相关的信息。每一个子目录有一个makefile文件,子目录makefile文件根据上级目录makefile文件命令启动编译。这些makefile使用.config文件配置数据构建各种文件列表,并使用这些文件列表编译内嵌或模块目标文件。scripts/Makefile.*包含了所有的定义和规则,与makefile文件一起编译出内核程序。
=== 2 角色分工
人们与内核makefile存在四种不同的关系:
*用户* 用户使用"make menuconfig"或"make"命令编译内核。他们通常不读或编辑内核makefile文件或其他源文件。
*普通开发者* 普通开发者维护设备驱动程序、文件系统和网络协议代码,他们维护相关子系统的makefile文件,因此他们需要内核makefile文件整体性的一般知识和关于kbuild公共接口的详细知识。
*体系开发者* 体系开发者关注一个整体的体系架构,比如sparc或者ia64。体系开发者既需要掌握关于体系的makefile文件,也要熟悉内核makefile文件。
*内核开发者* 内核开发者关注内核编译系统本身。他们需要清楚内核makefile文件的所有方面。
本文档的读者对象是普通开发者和系统开发者。
=== 3 内核编译文件
内核中大多数makefile文件是使用kbuild基础架构的makefile文件。本章介绍kbuild的makefile中的语法。
3.1节“目标定义”是一个快速导引,后面各章有详细介绍和实例。
--- 3.1 目标定义
     目标定义是makefile文件的主要部分(核心)。这些目标定义行定义了如何编译文件,特殊的兼容选项和递归子目录。
      最简单的makefile文件只包含一行:
     Example: obj-y += foo.o
    这行告诉kbuild在该目录下名为foo.o的目标文件(object),foo.o通过编译foo.c或者foo.S而得到。
    如果foo.o编译成一个模块,则使用obj-m变量,因此常见写法如下:
     Example: obj-$(CONFIG_FOO) += foo.o
     $(CONFIG_FOO)可以代表y(built-in对象)或m(module对象)。
      如果CONFIG_FOO不是y或m,那么这个文件不会被编译和链接。
--- 3.2 内嵌对象 - obj-y
    makefile文件将为编译vmlinux的目标文件放在$(obj-y)列表中,这些列表依赖于内核配置。
      Kbuild编译所有的$(obj-y)文件,然后调用"$(LD) -r"合并这些文件到一个built-in.o文件中。built-in.o经过父makefile文件链接到vmlinux。$(obj-y)中的文件顺序很重要。列表中文件允许重复,文件第一次出现将被链接到built-in.o,后续出现该文件将被忽略。
      链接顺序之所以重要是因为一些函数在内核引导时将按照他们出现的顺序被调用,如函数(module_init() / __initcall)。所以要牢记改变链接顺序意味着也要改变SCSI控制器的检测顺序和重数磁盘。
      例如: #drivers/isdn/i4l/Makefile
     # 内核ISDN子系统和设备驱动程序Makefile
     # 每个配置项是一个文件列表
     obj-$(CONFIG_ISDN)         += isdn.o
     obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o
--- 3.3 可加载模块 - obj-m
  $(obj-m)表示对象文件(object files)编译成可加载的内核模块。
  一个模块可以通过一个源文件或几个源文件编译而成。makefile只需简单地它们加到$(obj-m)。
      例如:#drivers/isdn/i4l/Makefile
        obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o
    注意:在这个例子中$(CONFIG_ISDN_PPP_BSDCOMP)含义是'm'。
      如果内核模块通过几个源文件编译而成,使用以上同样的方法。
      Kbuild需要知道通过哪些文件编译模块,因此需要设置一个$(-objs)变量。
    例如:#drivers/isdn/i4l/Makefile
     obj-$(CONFIG_ISDN) += isdn.o
     isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o
     在这个例子中,模块名isdn.o. Kbuild首先编译$(isdn-objs)中的object文件,然后运行"$(LD) -r"将列表中文件生成isdn.o.
  Kbuild使用后缀-objs、-y识别对象文件。这种方法允许makefile使用CONFIG_符号值确定一个object文件是否是另外一个object的组成部分。
     例如: #fs/ext2/Makefile
        obj-$(CONFIG_EXT2_FS)     += ext2.o
        ext2-y := balloc.o bitmap.o
        ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o
     在这个例子中,如果$(CONFIG_EXT2_FS_XATTR)表示'y',则ext2.o只有xattr.o组成部分。
     注意: 当然,当你将对象文件编译到内核时,以上语法同样有效。因此,如果CONFIG_EXT2_FS=y,Kbuild将先编译ext2.o文件,然后链接到built-in.o。
--- 3.4 导出符号目标
      在makefile文件中没有特别导出符号的标记。
--- 3.5 库文件 - lib-y
      obj-*中的object文件用于模块或built-in.o编译。object文件也可能编译到库文件中--lib.a。
      所有罗列在lib-y中的object文件都将编译到该目录下的一个单一的库文件中。
      包含在0bj-y中的object文件如果也列举在lib-y中将不会包含到库文件中,因为他们不能被访问。但lib-m中的object文件将被编译进lib.a库文件。
      注意在相同的makefile中可以列举文件到buit-in内核中也可以作为库文件的一个组成部分。因此在同一个目录下既可以有built-in.o也可以有lib.a文件。
      例如:#arch/i386/lib/Makefile
        lib-y   := checksum.o delay.o
     这样将基于checksum.o、delay.o创建一个lib.a文件。
      对于内核编译来说,lib.a文件被包含在libs-y中。将“6.3 目录表”。
      lib-y通常被限制使用在lib/和arch/*/lib目录中。
--- 3.6 目录递归
     makefile文件负责编译当前目录下的目标文件,子目录中的文件由子目录中的makefile文件负责编译。编译系统将使用obj-y和obj-m自动递归编译各个子目录中文件。
     如果ext2是一个子目录,fs目录下的makefile将使用以下赋值语句是编译系统编译ext2子目录。
     例如: #fs/Makefile
        obj-$(CONFIG_EXT2_FS) += ext2/
     如果CONFIG_EXT2_FS设置成'y(built-in)或'm'(modular),则对应的obj-变量也要设置,内核编译系统将进入ext2目录编译文件。
      内核编译系统只使用这些信息来决定是否需要编译这个目录,子目录中makefile文件规定那些文件编译为模块那些是内核内嵌对象。
      当指定目录名时使用CONFIG_变量是一种良好的做法。如果CONFIG_选项不为'y'或'm',内核编译系统就会跳过这个目录。
--- 3.7 编译标记
  EXTRA_CFLAGS, EXTRA_AFLAGS, EXTRA_LDFLAGS, EXTRA_ARFLAGS
  所有的EXTRA_变量只能使用在定义该变量后的makefile文件中。EXTRA_变量被makefile文件所有的执行命令语句所使用。
     $(EXTRA_CFLAGS)是使用$(CC)编译C文件的选项。
     例如: # drivers/sound/emu10k1/Makefile
           EXTRA_CFLAGS += -I$(obj)
           ifdef
            DEBUG EXTRA_CFLAGS += -DEMU10K1_DEBUG
            endif
     定义这个变量是必须的,因为顶层makefile定义了$(CFLAGS)变量并使用该变量编译整个代码树。
     $(EXTRA_AFLAGS)是每个目录编译汇编语言源文件的选项。
     例如: #arch/x86_64/kernel/Makefile
           EXTRA_AFLAGS := -traditional
     $(EXTRA_LDFLAGS)和$(EXTRA_ARFLAGS)用于每个目录的$(LD)和$(AR)选项。
     例如: #arch/m68k/fpsp040/Makefile
           EXTRA_LDFLAGS := -x
  CFLAGS_$@, AFLAGS_$@
     CFLAGS_$@和AFLAGS_$@只使用到当前makefile文件的命令中。
     $(CFLAGS_$@)定义了使用$(CC)的每个文件的选项。$@部分代表该文件。
     例如: # drivers/scsi/Makefile
           CFLAGS_aha152x.o =   -DAHA152X_STAT -DAUTOCONF
           CFLAGS_gdth.o   = # -DDEBUG_GDTH=2 -D__SERIAL__ -D__COM2__ \
  -DGDTH_STATISTICS CFLAGS_seagate.o =   -DARBITRATE -DPARITY -DSEAGATE_USE_ASM
     这三行定义了aha152x.o、gdth.o和seagate.o文件的编译选项。
     $(AFLAGS_$@)使用在汇编语言代码文件中,具有同上相同的含义。
     例如: # arch/arm/kernel/Makefile
           AFLAGS_head-armv.o := -DTEXTADDR=$(TEXTADDR) -traditional
           AFLAGS_head-armo.o := -DTEXTADDR=$(TEXTADDR) -traditional
--- 3.9 依赖关系
     内核编译记录如下依赖关系:
      1) 所有的前提文件(both *.c and *.h)
      2) CONFIG_ 选项影响到的所有文件
      3) 编译目标文件使用的命令行
     因此,假如改变$(CC)的一个选项,所有相关的文件都要重新编译。
--- 3.10 特殊规则
      特殊规则使用在内核编译需要规则定义而没有相应定义的时候。典型的例子如编译时头文件的产生规则。其他例子有体系makefile编译引导映像的特殊规则。特殊规则写法同普通的Make规则。
     Kbuild(应该是编译程序)在makefile所在的目录不能被执行,因此所有的特殊规则需要提供前提文件和目标文件的相对路径。
     定义特殊规则时将使用到两个变量:
  $(src): $(src)是对于makefile文件目录的相对路径,当使用代码树中的文件时使用该变量$(src)。
  $(obj): $(obj)是目标文件目录的相对路径。生成文件使用$(obj)变量。
     例如: #drivers/scsi/Makefile
     $(obj)/53c8xx_d.h: $(src)/53c7,8xx.scr $(src)/script_asm.pl
        $(CPP) -DCHIP=810 - -objs)包含了所有的用于链接最终可执行程序的对象。
     例如: #scripts/lxdialog/Makefile
        hostprogs-y   := lxdialog
        lxdialog-objs := checklist.o lxdialog.o
     扩展名.o文件都编译自对应的.c文件。在上面的例子中checklist.c编译成checklist.o,lxdialog.c编译为lxdialog.o。最后两个.o文件链接成可执行文件lxdialog。
     注意:语法-y不能用于定义主机程序。
--- 4.3 定义共享库
     扩展名为.so的对象是共享库文件,并且是位置无关的object文件。内核编译系统提供共享库使用支持,但使用方法有限制。在下面例子中libkconfig.so库文件被链接到可执行文件conf中。
     例如: #scripts/kconfig/Makefile
           hostprogs-y   := conf
           conf-objs     := conf.o libkconfig.so
           libkconfig-objs := expr.o type.o
     共享库文件需要对应的-objs定义, 在上面例子中库libkconfig由两个对象组成:expr.o和type.o。expr.o和type.o将被编译为位置无关代码并被链接如libkconfig.so。共享库不支持C++语言。
--- 4.4 C++语言使用方法
     内核编译系统提供了对C++主机程序的支持以用于内核配置,但不主张其它方面使用这种方法。
     例如: #scripts/kconfig/Makefile
           hostprogs-y   := qconf
           qconf-cxxobjs := qconf.o
     在上面例子中可执行文件由C++文件qconf.cc组成 - 通过$(qconf-cxxobjs)标识。
     如果qconf由.c和.cc文件混合组成,附加行表示这种情况。
     例如: #scripts/kconfig/Makefile
           hostprogs-y   := qconf
           qconf-cxxobjs := qconf.o
           qconf-objs   := check.o
--- 4.5 辅助程序编译控制选项
     当编译主机程序时仍然可以使用$(HOSTCFLAGS)设置编译选项传递给$(HOSTCC)。这些选项将影响所有使用变量HOST_EXTRACFLAG的makefile创建的主机程序。
     例如: #scripts/lxdialog/Makefile
           HOST_EXTRACFLAGS += -I/usr/include/ncurses
     为单个文件设置选项使用下面方式:
     例如: #arch/ppc64/boot/Makefile
     HOSTCFLAGS_piggyback.o := -DKERNELBASE=$(KERNELBASE)
     也可以使用附加链接选项:
     例如: #scripts/kconfig/Makefile
           HOSTLOADLIBES_qconf := -L$(QTDIR)/lib
     当链接qconf时将使用外部选项"-L$(QTDIR)/lib"。
--- 4.6 何时建立辅助程序
     只有当需要时内核编译系统才会编译主机程序。有两种方式:
     (1) 在特殊规则中作为隐式的前提需求
     例如: #drivers/pci/Makefile
     hostprogs-y := gen-devlist
     $(obj)/devlist.h: $(src)/pci.ids $(obj)/gen-devlist
              ( cd $(obj); ./gen-devlist )  产生 .config文件
2) 保存内核版本到include/linux/version.h文件中
3) 符号链接include/asm to include/asm-$(ARCH)
4) 更新所有目标对象的其它前提文件
  - 附加前提文件定义在arch/$(ARCH)/Makefile文件中
5) 递归进入init-* core* drivers-* net-* libs-*中的所有子目录和编译所有的目标对象
  - 上面变量值都引用到arch/$(ARCH)/Makefile文件。
6) 链接所有的object文件生成vmlinux文件,vmlinux文件放在代码树根目录下。
  最开始链接的几个object文件列举在arch/$(ARCH)/Makefile文件的head-y变量中。
7) 最后体系makefile文件定义编译后期处理规则和建立最终的引导映像bootimage。
  - 包括创建引导记录
  - 准备initrd映像和相关处理
--- 6.1 变量设置
  LDFLAGS      $(LD)一般选项
     选项使用于链接器的所有调用中。通常定义emulation就可以了。
     例如: #arch/s390/Makefile
           LDFLAGS   := -m elf_s390
      注意: EXTRA_LDFLAGS和LDFLAGS_$@可以进一步订制使用选项,将第7章。
  LDFLAGS_MODULE       $(LD)链接模块的选项
     LDFLAGS_MODULE通常设置$(LD)链接模块的.ko选项。
     默认为"-r"即可重定位输出文件。
  LDFLAGS_vmlinux   $(LD)链接vmlinux选项
     LDFLAGS_vmlinux定义链接最终vmlinux时链接器的选项。
     LDFLAGS_vmlinux支持使用LDFLAGS_$@。
     例如: #arch/i386/Makefile
           LDFLAGS_vmlinux := -e stext
  OBJCOPYFLAGS      objcopy选项
     当使用$(call if_changed,objcopy)转化a .o文件时,OBJCOPYFLAGS中的选项将被使用。
     $(call if_changed,objcopy)经常被用作为vmlinux产生原始的二进制文件。
     例如: #arch/s390/Makefile
           OBJCOPYFLAGS := -O binary
          #arch/s390/boot/Makefile
           $(obj)/image: vmlinux FORCE $(call if_changed,objcopy)
     在上面例子中$(obj)/image是vmlinux的二进制版本文件。$(call if_changed,xxx)
的使用方法见后。
  AFLAGS   $(AS)汇编选项
     默认值见顶层Makefile文件
     针对每个体系需要另外添加和修改它。
     例如: #arch/sparc64/Makefile
           AFLAGS += -m64 -mcpu=ultrasparc
  CFLAGS      $(CC)编译器选项
     默认值见顶层Makefile文件
     针对每个体系需要另外添加和修改它。
     通常CFLAGS变量值取决于内核配置。
     例如: #arch/i386/Makefile
           cflags-$(CONFIG_M386) += -march=i386
           CFLAGS += $(cflags-y)
     许多体系Makefiles文件动态启动市场目标机器上的C编译器检测支持的选项:
           #arch/i386/Makefile
           ...
           cflags-$(CONFIG_MPENTIUMII)   += $(call cc-option,\
                 -march=pentium2,-march=i686) ...
           # Disable unit-at-a-time mode ...
           CFLAGS += $(call cc-option,-fno-unit-at-a-time)
           ...
     第一个例子当config选项是'y'时将被选中。
  CFLAGS_KERNEL      $(CC)编译built-in对象的选项
     $(CFLAGS_KERNEL)包含外部C编译器选项编译本地内核代码。
  CFLAGS_MODULE      $(CC)编译模块选项
     $(CFLAGS_MODULE)包含外部C编译器选项编译可加载内核代码。
--- 6.2 增加预设置项
     prepare: 这个规则用于列举开始进入子目录编译前需要的前提文件。通常是些包含汇编常量的头文件。
     例如:
     #arch/s390/Makefile
     prepare: include/asm-$(ARCH)/offsets.h
     在这个例子中include/asm-$(ARCH)/offsets.h将在进入子目录前编译。
      详见XXX-TODO文件描述了kbuild如何产生offset头文件。
--- 6.3 目录表
     体系makefile文件和顶层makefile文件共同定义了如何建立vmlinux文件的变量。注意没有体系相关的模块对象定义部分:所有的模块对象都是体系无关的。
  head-y, init-y, core-y, libs-y, drivers-y, net-y
     $(head-y) 列举首先链接到vmlinux的对象文件。
     $(libs-y) 列举了能够找到lib.a文件的目录。
     其余的变量列举了能够找到内嵌对象文件的目录。
     $(init-y) 列举的对象位于$(head-y)对象之后。
     然后是如下位置秩序:
     $(core-y), $(libs-y), $(drivers-y) 和 $(net-y)。
     顶层makefile定义了所有同用目录,arch/$(ARCH)/Makefile文件只需增加体系相关的目录。
     例如: #arch/sparc64/Makefile
           core-y += arch/sparc64/kernel/
           libs-y += arch/sparc64/prom/ arch/sparc64/lib/
           drivers-$(CONFIG_OPROFILE) += arch/sparc64/oprofile/
--- 6.4 引导映像
     体系makefile文件定义了编译vmlinux文件的目标对象,将它们压缩和封装成引导代码,并复制到合适的位置。这包括各种安装命令。如何定义实际的目标对象无法为所有的体系结构提供标准化的方法。
     附加处理过程常位于arch/$(ARCH)/下的boot/目录。
     内核编译系统无法在boot/目录下提供一种便捷的方法创建目标系统文件。因此arch/$(ARCH)/Makefile要调用make命令在boot/目录下建立目标系统文件。建议使用的方法是在arch/$(ARCH)/Makefile中设置调用,并且使用完整路径引用arch/$(ARCH)/boot/Makefile。
     例如: #arch/i386/Makefile
           boot := arch/i386/boot
           bzImage: vmlinux
              $(Q)$(MAKE) $(build)=$(boot) $(boot)/$@
     建议使用"$(Q)$(MAKE) $(build)="方式在子目录中调用make命令。
     没有定义体系目标系统文件的规则,但执行"make help"命令要列出所有目标系统文件,因此必须定义$(archhelp)变量。
     例如: #arch/i386/Makefile
     define
        archhelp echo '* bzImage     - Image (arch/$(ARCH)/boot/bzImage)'
      endef
     当执行不带参数的make命令时,将首先编译第一个目标对象。在顶层makefile中第一个目标对象是all:。
     一个体系结构需要定义一个默认的可引导映像。
     "make help"命令的默认目标是以*开头的对象。
     增加新的前提文件给all目标可以设置不同于vmlinux的默认目标对象。
     例如: #arch/i386/Makefile
           all: bzImage
     当执行不带参数的"make"命令时,bzImage文件将被编译。
--- 6.5 编译非内核目标
  extra-y
     extra-y定义了在当前目录下创建没有在obj-*定义的附加的目标文件。
     在extra-y中列举目标是处于两个目的:
      1) 是内核编译系统在命令行中检查变动情况
        - 当使用$(call if_changed,xxx)时
      2) 内核编译系统知道执行"make clean"命令时删除哪些文件
     例如: #arch/i386/kernel/Makefile
           extra-y := head.o init_task.o
     上面例子extra-y中的对象文件将被编译但不会练接到built-in.o中。
--- 6.6 编译引导映像命令
     Kbuild提供了一些编译引导映像有用的宏。
  if_changed
     if_changed是后面命令使用的基础。
     用法:
        target: source(s)
            FORCE $(call if_changed,ld/objcopy/gzip)
     当这条规则被使用时它将检查哪些文件需要更新,或命令行被改变。后面这种情况将迫使重新编译编译选项被改变的执行文件。使用if_changed的目标对象必须列举在$(targets)中,否则命令行检查将失败,目标一直会编译。
     赋值给$(targets)的对象没有$(obj)/前缀。
     if_changed也可以和定制命令配合使用,见6.7"kbuild定制命令"。
     注意: 一个常见错误是忘记了FORCE前导词。
  ld
      链接目标。常使用LDFLAGS_$@作为ld的选项。
  objcopy
      复制二进制文件。常用于arch/$(ARCH)/Makefile中和使用OBJCOPYFLAGS作为选项。
     也可以用OBJCOPYFLAGS_$@设置附加选项。
  gzip
      压缩目标文件。使用最大压缩算法压缩目标文件。
     例如: #arch/i386/boot/Makefile
     LDFLAGS_bootsect := -Ttext 0x0 -s --oformat binary
     LDFLAGS_setup   := -Ttext 0x0 -s --oformat binary -e begtext
     targets += setup setup.o bootsect bootsect.o
     $(obj)/setup $(obj)/bootsect: %: %.o FORCE
            $(call if_changed,ld)
      在上面例子中有两个可能的目标对象,分别需要不同的链接选项。使用LDFLAGS_$@语法为每个目标对象设置不同的链接选项。
     $(targets)包含所有的目标对象,因此内核编译系统知道所有的目标对象并且将:
      1) 检查命令行的改变情况
      2) 执行make clean命令时删除目标对象
     ": %: %.o"是简写方法,减写setup.o和bootsect.o文件。
     注意: 常犯错误是忘记"target :="语句,导致没有明显的原因目标文件被重新编译。
--- 6.7 定制编译命令
     当执行带KBUILD_VERBOSE=0参数的编译命令时命令的简短信息会被显示。要让定制命令具有这种功能需要设置两个变量:
     quiet_cmd_ - 将被显示的内容
      cmd_      - 被执行的命令
     例如: #
           quiet_cmd_image = BUILD   $@
            cmd_image = $(obj)/tools/build $(BUILDFLAGS) \
                    $(obj)/vmlinux.bin > $@
           targets += bzImage
           $(obj)/bzImage: $(obj)/vmlinux.bin $(obj)/tools/build FORCE
                $(call if_changed,image)
                @echo 'Kernel: $@ is ready'
     执行"make KBUILD_VERBOSE=0"命令编译$(obj)/bzImage目标时将显示:
     BUILD   arch/i386/boot/bzImage
--- 6.8 预处理连接脚本
     当编译vmlinux映像时将使用arch/$(ARCH)/kernel/vmlinux.lds链接脚本。
     相同目录下的vmlinux.lds.S文件是这个脚本的预处理的变体。内核编译系统知晓.lds文件并使用规则*lds.S -> *lds。
     例如: #arch/i386/kernel/Makefile
           always := vmlinux.lds
           #Makefile
           export CPPFLAGS_vmlinux.lds += -P -C -U$(ARCH)
     $(always)赋值语句告诉编译系统编译目标是vmlinux.lds。$(CPPFLAGS_vmlinux.lds)赋值语句告诉编译系统编译vmlinux.lds目标的编译选项。
     编译*.lds时将使用到下面这些变量:
     CPPFLAGS      : 定义在顶层Makefile
     EXTRA_CPPFLAGS      : 可以设置在编译的makefile文件中
     CPPFLAGS_$(@F) : 目标编译选项。注意要使用文件全名。
--- 6.9 $(CC)支持功能
     内核可能会用不同版本的$(CC)进行编译,每个版本有不同的性能和选项,内核编译系统提供基本的支持用于验证$(CC)选项。$(CC)通常是gcc编译器,但其它编译器也是可以。
  cc-option cc-option 用于检测$(CC)是否支持给定的选项,如果不支持就使用第二个可选项。
     例如: #arch/i386/Makefile
           cflags-y += $(call cc-option,-march=pentium-mmx,-march=i586)
     在上面例子中如果$(CC)支持-march=pentium-mmx则cflags-y等于该值,否则等于-march-i586。如果没有第二个可选项且第一项不支持则cflags-y没有被赋值。
  cc-option-yn cc-option-yn用于检测gcc是否支持给定的选项,支持返回'y'否则'n'。
     例如: #arch/ppc/Makefile
           biarch := $(call cc-option-yn, -m32)
           aflags-$(biarch) += -a32
           cflags-$(biarch) += -m32
     在上面例子中如果$(CC)支持-m32选项则$(biarch)设置为y。当$(biarch)等于y时,变量$(aflags-y)和$(cflags-y)将分别等于-a32和-m32。
  cc-option-align gcc版本>= 3.0用于定义functions、loops等边界对齐选项。
     gcc = 3.00
        cc-option-align = -falign
     例如:
        CFLAGS += $(cc-option-align)-functions=4
     在上面例子中对于gcc >= 3.00来说-falign-functions=4,gcc ,均为两位数字。例如gcc 3.41将返回0341。
     当一个特定$(CC)版本在某个方面有缺陷时cc-version是很有用的。例如-mregparm=3在一些gcc版本会失败尽管gcc接受这个选项。
     例如: #arch/i386/Makefile
           GCC_VERSION := $(call cc-version)
           cflags-y += $(shell \
           if [ $(GCC_VERSION) -ge 0300 ] ; then echo "-mregparm=3"; fi
     在上面例子中-mregparm=3只使用在版本大于等于3.0的gcc中。
=== 7 Kbuild变量
  顶层Makefile文件导出下面这些变量:
  VERSION, PATCHLEVEL, SUBLEVEL, EXTRAVERSION
     这几个变量定义了当前内核版本号。很少体系体系Makefiles文件直接使用他们,常用$(KERNELRELEASE)代替。
     $(VERSION)、$(PATCHLEVEL)和$(SUBLEVEL)定义了三个基本部分版本号,例如"2", "4",和"0"。这三个变量一直使用数值表示。
     $(EXTRAVERSION)定义了更细的补钉号,通常是短横跟一些非数值字符串,例如"-pre4"。
  KERNELRELEASE
     $(KERNELRELEASE)是一个单一字符如"2.4.0-pre4",适合用于构造安装目录和显示版本字符串。一些体系文件使用它用于以上目的。
  ARCH
     这个变量定义了目标系统体系结构,例如"i386"、“arm"、"sparc". 一些内核编译文件测试$(ARCH)用于确定编译哪个文件。默认情况下顶层Makefile文件设置$(ARCH)为主机相同的系统体系。当交叉编译编译时,用户可以使用命令行改变$(ARCH)值:
        make ARCH=m68k ...
  INSTALL_PATH
     这个变量定义了体系Makefiles文件安装内核映项和System.map文件的路径。
  INSTALL_MOD_PATH, MODLIB
     $(INSTALL_MOD_PATH)定义了模块安装变量$(MODLIB)的前缀。这个变量通常不在Makefile文件中定义,如果需要可以由用户添加。
     $(MODLIB)定义了模块安装目录。
     顶层Makefile定义$(MODLIB)为$(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)。用户可以使用命令行修改这个值。
=== 8 Makefile语言
  内核Makefiles设计目标用于运行GNU Make程序。Makefiles仅使用GNU Make提到的特性,但使用了较多的GNU扩展部分。
  GNU Make程序支持基本的列表处理功能。内核Makefiles文件结合"if"语句使用了简单的列表建立和维护功能。
  GNU Make程序有两种赋值操作符:":="和"="。 ":="执行时立即计算右值并赋值给左值。"="类似公式定义,当每次使用左值要被使用时计算右值并赋给它。
  一些情况中使用"="合适,而一些情况中使用":="才是正确选择。
=== 9 Credits
  Original version made by Michael Elizabeth Chastain,  Updates
by Kai Germaschewski  Updates by Sam Ravnborg
=== 10 TODO
- Describe how kbuild support shipped files with _shipped.
- Generating offset header files.
- Add more variables to section 7?

作者: compare2000    时间: 2014-10-23 16:03
本帖最后由 compare2000 于 2014-10-23 16:08 编辑

根据HR的有关安排,参加了一些社招和校招的面试工作,包括集体面试和业务(专业)面试。

         面试他人的过程,实际也是在面试自己。由己及人,由人及己。

     简单整理下之前到现在的几点零星体会。自勉或他勉,仅供参
【给应聘初试者的几点基本建议】

     今日做了一天的面试官,遇到一些有一定工作经验的求职者,但还是犯了一些低级错误,忍不住给几点建议供参考并与大家探讨。

1、简历要简,一页为好,呈现要点亮点即可;

2、目标岗位要明确,至少要事先研究其名称和职责要求;

3、换岗求职的原因、意愿要清晰,意愿的强烈程度常常是相互的(正/反相关);

4、英语要有所准备,至少要准备下自我介绍与项目类的内容。  



【社招招聘面试随感】

1、工作年限如果不伴随着职位的提升或相应的职业成熟,只能是负分; 简单重复的日历累积(而不是阅历增长)只是平庸的潜台词。年龄亦然。

2、正能量因素在比较中(集面中)效应明显。积极主动、开放进取、团队精神(融入度与合作性)、成就欲......谁都喜欢。等待、保守、沉默、不合群......尽管有时并非坏事,但大部分人更愿意打常规牌。

3、差异性是一把双刃剑,要慎用。可以脱颖而出,可以早夭出局。大家钟爱独特价值,但不敢乱用旁门左道和哗众取宠。

4、留学背景、大公司的历练、专业认证的把关......其影响潜移默化,在日常的表达呈现和解决问题上是可见一斑的。

5、微笑、礼貌……这些简单的待人接物在默默地发挥着作用,影响着受众的感知。  

【今日招聘面试小体悟】  

- 微笑是人际交往永远的利器;自然友好的眼神接触,是最好的见面礼。

- 主动、开放、分享、友善……所有面向他人和团队的优秀习惯,让人们在人群中发现你。这种人一入团队就能凸显。

- 自信、坚韧、沉稳、度量……所有向内修炼的良好特质,让自己散发个性魅力。这种人不需要多表现即可见。

- 面相和气场的差异,带来的不同直觉体验,这种第一感对人的认知影响很大。



【今日招聘面试小体悟】  

- 集体面试是“群殴”或“选秀”,业务面试则是“单挑”或“独角戏”。集面讲究脱颖而出,业面追求单点突破。呈现的侧重点不同。

- 不怕示弱,因为没有全才;

  关键是不忘展示强项所在。

- 不懂没关系,装懂才误事;

  不懂可以学,续航看能力。

- 深进去抓细节和重点(微观),跳出来理结构和逻辑(宏观),如此,才会融会贯通而理解,将知识结构化于脑中。深入浅出,则是理解的最高境界。接下来就是实践转化能力。

- 基础很重要,应该是深入骨髓的。没有人因为学了高数而忘记了123加减乘除,也不应该因为学了经济学而忘了会计要素。



【今日招聘面试小体悟】  

- 人生充满了偶然。 有些偶尔就是单纯的偶然,比如单独事件; 有些偶然中间有必然,比如统计意义下的重复。以平淡心看待偶然,以认真劲对待必然。

- 一个环节的单独面试的成败,在我看来,其中有很大的偶然因素。你的成败可能源于一个很小的原因:一个微笑、一个眼神、一句话、一个正式或随意的打扮、一次积极或消极的发言、一个突发奇想的观点、同伴或对手的一个失误或攻击、一位严苛或随和的考官、一道碰巧的容易或刁钻的考题……从这个意义上来说,大可不必在一个环节的得失上太纠结。尽力而为,成败在天。

- 然,就长远而言,多轮面试乃至后续工作的成功者,确实富有正的能量特质。所谓性格决定命运,这其中就已经包含了众多偶然中蕴藏的必然。从这个意义上来说,正向地修炼自身、打磨性格,是永远不变的必要。

- 招聘考核,确实应该客观评测和主观面试相结合,融科学和艺术于一体,才可能尽量减少偶然的偏差。


- 微笑是人际交往永远的利器;自然友好的眼神接触,是最好的见面礼。

- 主动、开放、分享、友善……所有面向他人和团队的优秀习惯,让人们在人群中发现你。这种人一入团队就能凸显。

- 自信、坚韧、沉稳、度量……所有向内修炼的良好特质,让自己散发个性魅力。这种人不需要多表现即可见。

- 面相和气场的差异,带来的不同直觉体验,这种第一感对人的认知影响很大。
——最后总结的是:人格魅力
作者: compare2000    时间: 2014-10-23 16:09
从一个个人员的面试(微观)的表现,整理出来一套逻辑和整体结构(宏观), 以人为镜,在做面试官观察别人的同时,也总结与人交往过程中哪些有益,哪些有弊。文采出众精彩。
深进去抓细节和重点(微观),跳出来理结构和逻辑(宏观),如此,才会融会贯通而理解,将知识结构化于脑中。深入浅出,则是理解的最高境界。
大学毕业后大大小小面了几十场各类面试,也较为深入的研究过多个行业多累岗位的宣讲会和招聘模式,楼主所说的这些,稍稍研究下就可以被化解。面到最后就是返璞归真。对于面试官,最主要的就是关注在群面表现环节,再考虑经历、证书等,排除第一印象的影响;良好的题目设计,而不是千篇一律看题目就知道讨论结果与讨论套路是极其必要的,我司的题库差太多对于面试者,就是排除掉杂念,破解题目自身的漏洞,这样就可以减少和其他竞争者(合作者)的PK而增大自己的胜率,至于什么时候该用什么样的方法说什么样的话,普遍来说,在面试失败15-20次后可以完成第一阶段的面试经验积累,可以攻克绝大部分普通的面试套路,在面试总数超过25次后达到大成,不是碰到非能力不及的,基本没问题。
0     核心标准永远只有一个,你符不符合我们的职责要求

1     多问一些开放性的问题。面上的,很考验思维能力

2     再问几个细节。看他细致不细致。点上的,能否把问题搞清楚

3     思维能力,表达能力,是最重要的两方面。

3.1 分类。回答问题不要上来就特别具体化,先分类。这是逻辑思维的最基本方法。不然一下子就乱了,深一脚浅一脚的

          能高阶到结构化思维、系统性思维,甚至战略思维当然更好。

3.2 表达清晰。慢一点不要仅

4     说话自信,声音稳重,绝对加分

5     多让面试者自己说。看他的局面掌控能力。有的人经常就不知道绕到哪里了

6     问题不是要打翻他,重点要通过问题发现他的能力与潜力。

7     经验当然重要,但世界变化这么快,我更看重你后续的学习能力

8     "提一些新问题,看他的问题处理能力,复杂场景能力。压力下的从容能力。

如果遇到XXX问题,你怎么处理?"
演员的自我修养 对自己永远要有要求。
作者: compare2000    时间: 2014-10-23 16:18
阿里分布式数据库服务原理与实践-shenxun.pdf ( 676.73 KB ,672 downs )   
实时计算服务化实践-qiangqi.pdf ( 8.01 MB ,447 downs )   
构建一个跨机房的Hadoop集群-wuwei.pptx ( 698.49 KB ,405 downs )   
无线互联网-产品质量保证-xuzheng.ppt ( 2.68 MB ,411 downs )   
探求计算性能极限-探求计算密集应用优化的天花板-wangzheng.pdf ( 881.78 KB ,346 downs )   
基于Hadoop_HBase的一淘搜索离线系统-wangfeng.pptx ( 1.05 MB ,401 downs )   
深度学习:机器学习的新浪潮-yukai.pdf ( 5.69 MB ,508 downs )   
百度媒体云技术及架构-chenbing.pdf ( 1.75 MB ,479 downs )   
后端-大型网站SEO优化实践-zhouwenjun.pdf ( 1.77 MB ,379 downs )   
去哪儿酒店实时搜索技术分享-liuyue.pdf ( 1.48 MB ,469 downs )   
淘宝消息中间件技术演变-zhanglewei.pdf ( 1.31 MB ,378 downs )   
AB测试和灰度发布平台架构设计和实践-oucheng.pdf ( 1.08 MB ,339 downs )   
淘宝网CRM平台架构for ADC-yunguang.pdf ( 4.32 MB ,340 downs )   
HouseMD介绍-jushi.pdf ( 360.25 KB ,274 downs )   
Inside TAE-liuhaoyu.pdf ( 3.3 MB ,308 downs )   
Nashorn与JDK8——动态语言在JVM上的高性能实现-mosu.pdf ( 1.09 MB ,307 downs )   
Scala中的函数式特征与并发-wanghongjiang.pdf ( 1.57 MB ,318 downs )   
TMS模块化页面搭建平台-fangjun.pdf ( 1.07 MB ,279 downs )   
Web页面分层交叉实验-chendong.pdf ( 792.51 KB ,279 downs )   
北冥神功- 新一代数据查询中间层-zhijiale.pdf ( 11.24 MB ,341 downs )   
互联网系统的稳定性保证:微博的实践-hongxiaojun.pdf ( 1.02 MB ,311 downs )   
基于OpenStack构建网易云主机服务-zhangxiaolong.pdf ( 1010.79 KB ,300 downs )   
架构模式与实践漫谈-wangfuqiang.pdf ( 12.51 MB ,406 downs )   
容量规划与授权限流降级-zhangjun.pdf ( 414.97 KB ,257 downs )   
淘宝文本中的语义分析以及技术展望-wangtianzhou.pdf ( 3.21 MB ,298 downs )   
天猫个性化推荐架构-zhangqi.pdf ( 2.34 MB ,339 downs )   
新一代前端代码开发与部属方案——独角兽系统功能简介-dengnanqiao.pptx ( 1.2 MB ,279 downs )   
让店铺飞起来-feichang.ppt ( 1.32 MB ,297 downs )   
商品详情页静态异步化-jicheng.pptx ( 876.04 KB ,256 downs )   
架起前端和开发的桥梁-chenzhongyin.ppt ( 4.14 MB ,306 downs )
作者: compare2000    时间: 2014-10-23 16:19
淘宝网商品详情页静态异步化-liujunyuan.ppt ( 1.31 MB ,255 downs )   
不一样的装修-wangyufeng.pptx ( 116.91 KB ,248 downs )   
跨终端的文件加载及缓存-liushuang.pdf ( 9.82 MB ,271 downs )   
开放JS在淘宝的应用-shiba.pdf ( 324.43 KB ,237 downs )   
你所不知道的TAE SDK-taogu.pdf ( 629.35 KB ,238 downs )   
DBFree-阿里数据库自动化运维平台-yangxu.pptx ( 1009.57 KB ,249 downs )   
CAP 理论与实践-tongjiawang.pptx ( 2.42 MB ,297 downs )   
iDB-阿里集团数据库服务平台-yezhengsheng.pptx ( 1.08 MB ,243 downs )   
MySQL at NetEase-jianchengyao.pdf ( 937.27 KB ,243 downs )   
OceanBase0.4:从API到SQL-yangchuanhui.pptx ( 1.05 MB ,249 downs )   
阿里巴巴离线大数据处理平台概述-tangzinan.pptx ( 2.51 MB ,311 downs )   
阿里“去IOE”实践-chenzhaoshan.pptx ( 1.47 MB ,290 downs )   
阿里数据同步前世今生-chenshouyuan.pptx ( 1.57 MB ,274 downs )   
大数据时代的月光宝盒-zhangmaosen.pptx ( 1.59 MB ,300 downs )   
分布式数据分析算法-yangxu2.pptx ( 1.01 MB ,291 downs )   
大众点评网高可用数据架构-lujunyi.pdf ( 302.2 KB ,271 downs )   
如何打造360 MySQL服务-yangting.pdf ( 1.57 MB ,270 downs )   
实时计算平台及相关业务实践-chaihua.pptx ( 236.78 KB ,244 downs )   
探索阿里数据藏宝图——数据地图管理实践-liuyingyao.pdf ( 1.64 MB ,297 downs )   
腾讯广点通的数据挖掘-xiaolei.pptx ( 2.08 MB ,273 downs )   
小公司的服务管理和监控成长之路-laiwei.pptx ( 1.78 MB ,289 downs )   
页面资源位监测和价值分析-zhaowenbo.pptx ( 6.13 MB ,245 downs )   
Cascadb TokuDB性能与适用场景分享-yigong.pptx ( 770.29 KB ,212 downs )   
Mysql高并发热点更新性能优化-liuhui.ppt ( 268.5 KB ,244 downs )   
开源软件的商业价值-michael.pdf ( 193.24 KB ,272 downs )   
CM–集群管理与负载均衡系统-sunquan.pptx ( 1020.42 KB ,226 downs )   
垂直抓取系统——EtaoSpider介绍-xiezhenliang.pptx ( 1.1 MB ,231 downs )   
个性化搜索技术和应用-chenxi.pptx ( 4.45 MB ,265 downs )   
关系搜索和推荐-ouwenwu.pptx ( 783.2 KB ,233 downs )   
海量商品的聚合技术及其在搜索中的应用-minghu.pptx ( 1009.46 KB ,240 downs )   
机器学习在搜索排序中的应用-zenganxiang.pptx ( 3.18 MB ,289 downs )   
搜索引擎和淘宝搜索不得不说的故事-zhengnan.pptx ( 1.05 MB ,265 downs )   
实时个性化系统和应用-linjianguo.pptx ( 1.09 MB ,233 downs )   
文本挖掘在电子商务场景中的应用-sunjian.pptx ( 3.17 MB ,262 downs )   
一站式搜索服务平台-Tsearcher-liuming.pptx ( 3.53 MB ,239 downs )   
Android设备体验优化-fengsenlin.pptx ( 1.06 MB ,274 downs )   
百度云推送介绍和架构分享-guozheng.pptx ( 3.73 MB ,283 downs )   
无线客户端数据实战-ninghaiyuan.pdf ( 8.49 MB ,272 downs )   
一个移动互联网技术创业的故事-haopeiqiang.ppt ( 8.58 MB ,302 downs )   
移动设备客户端安全-luojingjian.ppt ( 2.83 MB ,272 downs )
作者: compare2000    时间: 2014-10-23 16:19
移动系统中的Java虚拟机-xuweigang.pptx ( 339.14 KB ,213 downs )   
如何有效控制Andoid应用的耗电量-qianjiajing.pptx ( 936.48 KB ,220 downs )   
Lua wax在ios上的应用-huwenjiang.pptx ( 469.03 KB ,186 downs )   
Hybrid组建实践-WindVane项目简述-yujia.pptx ( 4.53 MB ,218 downs )   
Android自动化测试-jiale.pptx ( 885.66 KB ,255 downs )   
ios客户端稳定性-yudong.pptx ( 1.61 MB ,231 downs )   
android app性能优化之list篇-huangkun.ppt ( 1.42 MB ,247 downs )   
OS客户端应用测试实践-chenxu.pptx ( 2.38 MB ,227 downs )   
Fault injection实现原理与应用-liguodong.pptx ( 1.48 MB ,212 downs )  
iOS WEB自动化测试方案的探索与实践-banjun.pdf ( 24.27 MB ,347 downs )   
更好更快的Android自动化测试-luxiaoyu.pptx ( 1.02 MB ,304 downs )   
建设持续交付组织-jinming.pdf ( 2.78 MB ,321 downs )   
数据质量中心的设计与实现-xiemin.pptx ( 1.89 MB ,296 downs )   
无线质量全过程保障实践-xiapeifang.pptx ( 4.24 MB ,293 downs )   
不写代码实现UI自动化测试-xiadawei.pptx ( 7.48 MB ,330 downs )   
多浏览器兼容性测试平台xbrowser剖析-lihaijing.ppt ( 1.32 MB ,267 downs )   
前端无痛质量方案探索-pangeng.pdf ( 6.18 MB ,280 downs )   
淘宝电子商务云应用安全-liyonghui.pptx ( 2.43 MB ,294 downs )   
淘宝网应用性能管理的原理和实践-fangfei.pptx ( 863.6 KB ,282 downs )   
iOS WEB自动化测试方案的探索与实践-banjun.pdf ( 24.27 MB ,347 downs )   
更好更快的Android自动化测试-luxiaoyu.pptx ( 1.02 MB ,304 downs )   
建设持续交付组织-jinming.pdf ( 2.78 MB ,321 downs )   
数据质量中心的设计与实现-xiemin.pptx ( 1.89 MB ,296 downs )   
无线质量全过程保障实践-xiapeifang.pptx ( 4.24 MB ,293 downs )   
不写代码实现UI自动化测试-xiadawei.pptx ( 7.48 MB ,330 downs )   
多浏览器兼容性测试平台xbrowser剖析-lihaijing.ppt ( 1.32 MB ,267 downs )   
前端无痛质量方案探索-pangeng.pdf ( 6.18 MB ,280 downs )   
淘宝电子商务云应用安全-liyonghui.pptx ( 2.43 MB ,294 downs )   
淘宝网应用性能管理的原理和实践-fangfei.pptx ( 863.6 KB ,282 downs )   下
性能持续集成的探索和实践-xuxiao.pptx ( 765.44 KB ,171 downs )   
TCPFLOW业务网络服务质量分析-zhuyouzhi.pdf ( 1.98 MB ,185 downs )   
高性能网关产品的设计与实践-wangxinpu.pdf ( 844.18 KB ,176 downs )   
海量未知木马机器分析-huangzheng.pptx ( 119.37 KB ,162 downs )   
深入理解CPU的读写及优化-maling.pdf ( 944.57 KB ,200 downs )   
自动化运维和数据化运维-从硬盘开始-liuyi.pdf ( 1.41 MB ,202 downs )   
跨平台体验融合-shengbo.ppt ( 11.55 MB ,185 downs )   
设计之外 我们还追求什么样的设计-chenyizhang.pdf ( 2.8 MB ,198 downs )   
数据可视化设计-cuizhiwei.pdf ( 5.92 MB ,212 downs )   
Golden Age Of RTB-shengxuehua.pdf ( 1.58 MB ,169 downs )   下
Tanx业务发展及系统架构-suning.pdf ( 4.75 MB ,180 downs )
作者: compare2000    时间: 2014-10-23 16:20
阿里广告中的机器学习平台-jianglong.pdf ( 1.17 MB ,191 downs )   
个性化推荐技术在广告中的应用-zhaobinqiang.pdf ( 1.06 MB ,172 downs )   
淘宝联盟前端引擎技术发展-kejie.pdf ( 2.51 MB ,178 downs )   
时间-兴趣耦合的IPTV用户收视模型-zhangya.pdf ( 989.82 KB ,161 downs )   
搜索广告触发的挑战和应用-huyunhua.pdf ( 1006 KB ,150 downs )   
淘宝DMP平台-xucheng.pdf ( 1019.18 KB ,152 downs )   
实时定向广告技术-DSP框架及算法-lijunliang.pdf ( 1.46 MB ,176 downs )   
广告中的Match新架构和CVR预估-liuhualei.pptx ( 202.11 KB ,141 downs )   
广告引擎的索引设计实现和性能优化-liuyichuan.ppt ( 777.5 KB ,151 downs )   
canal产品介绍-loujianghang.ppt ( 1.47 MB ,178 downs )   
Druid数据库连接池介绍-wenshaojing.pptx ( 363.72 KB ,153 downs )   
Dubbo开源-liangfei.pptx ( 3.2 MB ,180 downs )   
Fastjson介绍-wenshaojing2.pptx ( 154.58 KB ,156 downs )   
Introduction to KISSY-heyiming.pdf ( 726.9 KB ,151 downs )   
MySQL复制-zaiweixiang.pptx ( 408.59 KB ,161 downs )   
OceanBase介绍-yangchuanhui2.pptx ( 1.08 MB ,168 downs )   
tengine介绍-yaoweibing2.pptx ( 4.08 MB ,180 downs )   
Tsar介绍-like.pptx ( 2.36 MB ,169 downs )   
OpenStack Summit Intel Efforts-wangqing.pdf ( 1.88 MB ,186 downs )   
纠错码技术在hadoop分布式文件系统的应用-jinjun.pdf ( 1.32 MB ,197 downs )   
利用固态硬盘解决存储瓶颈-nijingfeng.pdf ( 2.19 MB ,217 downs )
作者: compare2000    时间: 2014-10-23 16:20
本帖最后由 compare2000 于 2014-10-23 16:27 编辑

大家都很关心电软转型的机会和挑战,目前能看到有IoT、互联网金融、能力中心,而这些机会的核心还是基于数据资产的运营。感觉现在即使机会摆在我们面前,我们也未必能抓住,因为还没看清支撑这些机会的平台和架构。运营商还有哪些领域可以涉足,是不是该有个面向互联网、面向数据运营的OS,具体的技术和平台有哪些,


讨论输入:

1、IoT(健康医疗、智能家居、车联网、家电数字化)
1)关注对象:
google:android auto,android wear,android TV,Google Fit
apple:homekit、healthkit、car play
海尔、三星:数字家电
微信:社交融合智能家居
2)关键信息
行业动态、业务战略、商业模式、业务场景、方案&架构


2、中小企业数字化(公有云、能力中心)
1)关注对象:
IBM Bluemix、APIGee、阿里云
2)关键信息
战略布局、商业模式、生态系统、平台架构、相关技术


3、互联网金融
1)关注对象
阿里:支付宝、余额宝
联通:话费宝
电信:添益宝
银联:二维码支付技术
NTTDOCOMO:手机钱包(做的最好的移动支付、拓展到了信用卡、海外转移支付等业务)
2)关键信息
行业分析、商业模式、供应商、解决方案、功能架构、相关技术


4、电商(B2C、商场数字化使能)
1)关注对象
微信、微软/google/apple室内定位、NTTDOCOMO的dshop(全球运营商做的最好的电商)
2)关键信息
商业模式、生态系统、功能架构、相关技术


5、颠覆性商业模式、创新公司和技术——
C2B(尚品宅配,阿里很推崇的柔性制造)
Luvocracy,电商中间件
surf air,航空短途月票服务

作者: compare2000    时间: 2014-10-23 16:37
技术规划人员需要具备的能力和培训需求:

1)、在从事技术规划中遇到哪些问题?技术规划师应该具备什么样的能力?
2)、针对这些能力应该开展哪些培训?


1) 对公司产品流程和技术缺乏深入了解、对于开源和IT技术缺乏实践、咨询报告等信息获取缺乏有序的渠道。

2)对报告撰写缺乏经验,尤其是抽取宏观观点,整理大颗粒观点,撰写总结,感觉十分困难

3)技术规划师应该具备看趋势能力,总结能力,写报告,讲解能力,IT和开源技术能力。

4)培训:公司产品流程和技术的介绍、成熟的规划经验介绍、优秀PPT讲解、各种IT和开发的技术培训、
作为新手面对的问题有

1)调研切不中需求痛点,因为对产品需求和领域技术了解广度和深度都不够,缺少积累.

2)分析和总结的观点受众不明确,经常不知道给谁看,应该呈现什么

3)胶片撰写的技巧欠缺

技术规划师应该具备的

1)对自己产品框架、需求、问题的深刻了解

2)对行业趋势的长期跟踪积累的眼力(技术敏感性、趋势敏感性)

3)深入浅出的观点提炼能力

培训:

1)技术沙龙(产品情况和技术的普及性介绍,优秀技术洞察的分享,有针对性地对产品的单点具体问题深入沟通)

2)常规的技术规划技能培训


作者: compare2000    时间: 2014-10-24 11:58
什么是领导力?通俗的说,就是下属或者周边的人,愿意主动跟着你干。领导是榜样,体现的是个人感召力和影响力,而不是基于职位或者角色的强制要求。领导力无处不在,例如:父母自己看电视,要求小孩阅读,给你xx元买书,每天xx页,效果好吗?如果父母身体力行,主动阅读,用行动给孩子树立榜样,耳儒目染,孩子主动愿意阅读,效果是不是更好?自我管理从而影响他人,这就是领导力,公司类似。《礼记.大学》中的“修身、齐家、治国、平天下”,“修身”实际就是对塑造领导力的最好阐述。

如何通过自我管理来塑造领导力?詹姆斯.库泽斯的《领导力》发现了体现领导力的5种行为,通过自我管理做好5个领导力行为,就能够塑造领导力。库泽斯发现的5个领导力行为:以身作则、共启愿景、挑战现状、使众人行、激励人心。下面讨论如何通过自我管理来塑造5种领导行为。

以身作则,首先要明确自己的“则”是什么?如果没有自己明确清晰的理念,发自内心的声音,根本就不存在作则的问题。“则”很简单,例如:“开发质量一流的产品”。以身作则用自己的实际行动来展现自己的理念。你说要做质量一流的产品,时时刻刻的行为,就要支撑开发质量一流的产品,而不是遇到一点挑战,马上就妥协了。自我管理塑造领导力,最基本的就是要做到言行一致。

作者: compare2000    时间: 2014-10-24 11:58
共启愿景,需要找到大家的共同利益和目标,展望未来,想象令人激动人心的各种可能,并且自己深信不疑。仅仅自己深信不疑是不够的,要把自己这种信念,在各种场合,通过各种方式,描述这种美好的愿景,逐步感召他人一起为愿景奋斗。逐步把个人的理念变成他人的共同愿景,在这个过程中,就是一个不断自我管理,不断用自己的行为来感召和影响他人,持之以恒的形成大家共同的目标和愿景的过程,慢慢让他人自动自发的来跟随的。

挑战现状,首先自己不满足于现状,追求卓越的过程,主动追求变化,成长和发展,寻造突破和提升。其次,在寻求突破和提升的道路上,不断的尝试,努力取得一个个小的成功,在错误中学习,不断获得成长。自我管理就是用不服输的心,不断超越和突破。失去了挑战现状的心,也就失去了改变现状的机会,也就失去了感召他人为之奋斗的舞台,也就失去领导力展现的场景。

使众人行,首先需要压制个人成功的冲动、虚荣心和表现欲,通过成就他人来成就自己。其次,通过共同的目标,建立信任来促进合作,不断帮助他人成长,分享权力,增强他人的实力,通过他人的成长和成功,促进整个团队的成功。在现实的工作中,给他人更多的表现机会,更能发挥他人特长的工作机会,提供必要的资源和情感支撑等,这些本身就是领导力。

激励人心,首先要做好自我激励,不管事情的进展如何,把自己的信念转为强大的动力,时刻做好自我激励,在困难和成功中,都能找到激励自身的因素,点燃“内心之火”。其次,表彰卓越表现,认可他人贡献,让他人感受到对团队的价值和重要性,让他人感觉良好。开展庆祝活动,把庆祝活动和团队荣誉感、激励干劲联系在一起,享受成功来带的喜悦,彼此的感受,激励人心,在分享和支持中,让团队荣誉和激励获得升华,使庆祝活动成为新工作的成功起点。

领导力来自自我管理,“修身”才能“平天下”。自我管理做好以身作则、共启愿景、挑战现状、使众人行、激励人心5个行为,领导力不请自来。
作者: compare2000    时间: 2014-10-24 12:01
本帖最后由 compare2000 于 2014-10-24 12:02 编辑

领导力首先来自管理者的自我管理,德鲁克在《卓有成效的管理者》中也说:成功的管理者首先是管理好自己。
作为一个管理者,管理好自己,以身作则,才能管理好团队。管理者如何管理好自己?正如博主提到的“修身”,修身的方法与手段就是:格物,致知,诚意,正心。




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2