分类目录归档:shell编程

主要是Linux下一些命令详解及shell编程相关的文章

shell脚本. ./file.sh与./file.sh执行的区别

最近遇到一个软件,在安装时,要求先要执行. ./profile.sh,看到这个写法,着实迷惑了下,这个和./profile.sh有什么区别?先贴出脚本的简写版本:

# Set the operating system environment variable

if [ -x /bin/uname ]
then
OS=`/bin/uname`
export OS
else
echo
echo "What type of workstation are you using?"
echo
echo "Find the command uname, and insert the correct"
echo "information in the .profile_nawips file."
echo
exit 1
fi

# Initialize OS version and OS release to blank.
OS_VER=""
OS_REL=""

case $OS in

AIX )
# IBM
OS_VER=`uname -v`
;;

HP-UX )
# Hewlett-Packard
OS="HPUX"
export OS
OS_VER=`uname -r | cut -f2 -d.`
;;

IRIX|IRIX64 )
# Silicon Graphics
OS_VER=`uname -r | cut -f1 -d.`
OS_REL=`uname -r | cut -f2 -d.`
# Fix for IRIX64
if [ $OS = "IRIX64" ]
then
OS="IRIX"
export OS
fi
;;

Linux )
# Linux - PC
OS_VER=`uname -r | cut -f1 -d.`
OS_REL=`uname -r | cut -f2 -d.`
if [ $OS_REL = 6 ] && [ $MACHTYPE != x86_64 ]
then
OS_REL=4
fi
entrp=`uname -r | awk -F. '{print $NF}'| tr "[A-Z]" "[a-z]"`
if [ $entrp = "el" ] || [ $entrp = "elsmp" ]
then
OS_REL=`echo $OS_REL`el
fi
;;

SunOS )
# Sun Micro Systems
OS_VER=`uname -r | cut -f1 -d.`
;;

* )
echo "What type of UNIX do you have?"
echo
echo "Find the command uname, and insert the correct"
echo "information in the .profile_nawips file."
;;

esac

export OS_VER
export OS_REL

# Set NA_OS to the lowercase equivalent of OS
# Add the version number to the name and for Linux, also add the release number.
if [ $OS = "Linux" ]
then
NA_OS=`echo $OS | tr "[A-Z]" "[a-z]"``echo $OS_VER`.`echo $OS_REL`
else
NA_OS=`echo $OS | tr "[A-Z]" "[a-z]"``echo $OS_VER`
fi
export NA_OS

#========================================================================
# Compilation and link flags for F77 and CC.

CC="cc"

export CC

LD="ld"
export LD

AR="ar"
export AR

ARFLAGS="crv"
export ARFLAGS

RM="rm -f"
export RM

INCLUDES="-I$GEMINC -I$OS_INC"
export INCLUDES

case $OS in

AIX )
CFLAGS="$INCLUDES -D$OS"
LDFLAGS="-L$OS_LIB -s"
;;

HPUX )
CFLAGS="$INCLUDES -D$OS +DA1.1"
LDFLAGS="-L$OS_LIB +DA1.1 -s"
;;

IRIX )
CFLAGS="$INCLUDES -D$OS -DUNDERSCORE -I/usr/Motif-2.1/include"
LDFLAGS="-L$OS_LIB -L/usr/Motif-2.1/lib32 -s -woff 85"
;;

Linux )
CC="gcc"
export CC
CFLAGS="$INCLUDES -D$OS -DUNDERSCORE -I/usr/X11R6/include"
LDFLAGS="-L$OS_LIB -L/usr/X11R6/lib"
if [ $MACHTYPE = "x86_64" ]
then
LDFLAGS="-L$OS_LIB -L/usr/X11R6/lib64"
elif [ $NA_OS = "linux2.4" ]
then
LDFLAGS="$LDFLAGS -static -s"
fi
;;

SunOS )
CFLAGS="$INCLUDES -D$OS -DUNDERSCORE"
LDFLAGS="-L$OS_LIB -s"
;;

* )
echo
echo "WARNING..."
echo
echo "No compiler options have been set."
echo
;;

esac

export CFLAGS
export LDFLAGS

之所以贴出这么长的脚本例子,主要觉得这个脚本处理跨平台的情况很具有参考性,其实脚本就是做了个导出操作系统版本和编译选项的动作。好了,我们使用两种情况执行一下脚本,看一下情况吧,毕竟动手胜于雄辩嘛

$ echo $CFLAGS

$ sh ./profile.sh
$ echo $CFLAGS

$ . ./profile.sh
$ echo $CFLAGS
-I -I -DLinux -DUNDERSCORE -I/usr/X11R6/include
$ vi profile.sh

我们先使用./profile.sh的情况执行(sh ./profile.sh),然后查看环境变量,为空,然后使用. ./profile.sh的方法执行,然后再看,当前的环境变量改变了。
这个结果比较明显了,使用. ./profile.sh的方法执行脚本会等同于source profile.sh,实际上就是在当前的bash环境执行,因此会改变当前的bash环境,而使用./profile.sh和sh ./profile.sh的方法执行,我们通过命令(sh ./profile.sh)也可以猜想下:其实是重新启动了一个bash子进程,然后执行了bash脚本,所以当然不会改变当前的环境变量了。

你真的了解sort命令吗?——sort命令排序详解

这两天在把一个2.3亿(导成文本后,大概有42GB)左右的数据从infobright中拉取出来,然后进行排序,去重,由于要用到排序,首先自然是sort,在使用过程中,才发现,原来对sort命令的了解,真的只是皮毛。

本文不讲解sort命令的详解参数,只讲实例,如果需要了解详细参数,可以自己man、google或baidu之。

本文使用的示例文本

100  1100 5000
100  2    5000
100  110  5000
10   100  5000
3    50   3000
2    100  4800
2    100  4500

列数M=3
系统为CentOS 5.3 x64,sort版本为V5.97

第一个例子:
最原始的
命令:

sort t.txt

结果:

100  1100 5000
100  110  5000
100  2    5000
10   100  5000
2    100  4500
2    100  4800
3    50   3000

分析: sort命令按照第1个、第2个、第N个字段依次排序,如果第一个字段相同,就按第2个字段的顺序排列,直到最后。

第二个例子
对第二个字段进行排序(如不特别指明,都是升序)
命令:

sort t.txt -k2,2

结果:

10   100  5000
2    100  4500
2    100  4800
100  110  5000
100  1100 5000
100  2    5000
3    50   3000

分析:原来一直以为,按第二个字段进行排序,使用sort t.txt -k2 就行了,实际情况并非如此,-k2时,实际上相当于-k2,M(后面的M,一直指代文件的最大列数),也就是对第2列排序后,还会对第3列、第4列直到最后一列进行排序

sort t.txt -k2

结果为:

2    100  4500
2    100  4800
10   100  5000
100  1100 5000
100  110  5000
100  2    5000
3    50   3000

第三个例子
对第二个字段按数值进行排序
命令:

sort t.txt -k2,2n

结果:

100  2    5000
3    50   3000
10   100  5000
2    100  4500
2    100  4800
100  110  5000
100  1100 5000

分析:发现结果很诡异吧?第一列、第三列居然都有问题!!!是因为sort有一个resort重排序机制,对指定的第二列优先排序后,sort马上回到第一列排,完成后,马上跳过指定的第二列,对第三列进行排序。因为这个原因,所以它打乱了原始文件的数据顺序,如果不希望这种情况出现,想要保证原始顺序,需要使用-s 选项来阻止resort机制!

第四个例子
对第2-3个字段按数值进行排序
命令:

sort t.txt -k2,3n

结果:

100  2    5000
3    50   3000
10   100  5000
2    100  4500
2    100  4800
100  110  5000
100  1100 5000

分析:发现诡异了吗?明明指定的是2-3列按数值排序,结果第三列木有干活,是的,这就是数值排序的特殊之处,只对指定的第一列有效,除此之外的列是无效的,排完指定的第2列后,返回第一列,进行resort,当然,要阻止还是使用-s。
那如果我想对第3列也使用数值排序呢?是的,你只有使用下面的苦逼办法——一列一列的写。

sort t.txt -k2,2n -k3,3n

第五个例子
根据实际的使用经验,在进行大文件排序时,最好对大文件进行分解,分解成N个小文件(多小呢?看机器性能,在我们的生产环境——8CPU、32G Mem,我测试的是,80M一个文件的排序是最适当的),排序完成后,再对小文件进行合并,这样会快好多,而对单个大文件的排序,sort效率很差。
另外,文件太大,sort会使用部分很大的临时文件,一般在/tmp下,如果/tmp空间不足,可以使用 -T 来指定一个临时文件的存放目录

使用date为打包文件加上时间

有时需要打包一个文件,传送给别人,但是直接将文件打包成.tar.gz或者.tar.bz2发送后,如果有更改,在此发送,就可能造成名字的混淆,这时如果在文件名后加上时间就清晰了,具体方法如:
tar zcvf filename`date “+%Y%m%d%H%M%S”`.tar.gz files
tar jcvf filename`date “+%Y%m%d%H%M%S”`.tar.bz2 files

硬盘读写查看工具

硬盘读写查看工具iotop
如果你知道有程序在磨你的硬盘,但是你又不能确定是哪一个程序在磨你的硬盘,那么就用 iotop来帮助你吧。

在Ubuntu里安装命令是:

sudo apt-get install iotop

从源码安装(点此下载)
由于其是基于python编写的,需要python-curses模块支持,所以先要安装python-curses模块,同时由于功能本身的限制,需要内核版本大于2.6.20。

tar jxvf  iotop-0.4.3.tar.bz2
cd iotop-0.4.3
python  setup.py build
python  setup.py install

安装好之后在终端输入:iotop就可以了

下面来说一具体运用:

可以用左右箭头操作,按 r 是相反方向,按 o 是动态切换

用法 iotop -参数

–version 查看版本信息的

-h, –help 查看帮助信息的

-o, –only 只显示在划硬盘的程序
-b, –batch 批量处理 用来记录日志的

-n NUM 设定循环几次

-d SEC, –delay=SEC 设定显示时间间隔

Linux排序命令sort详解

Linux排序命令sort详解

语法格式sort [ -A ] [ -b ] [ -c ] [ -d ] [ -f ] [ -i ] [ -m] [ -n ] [ -r ] [ -u ] [ -o OutFile ]
[ -t Character ] [ -T Directory ] [ -y [ Kilobytes ] ] [ -z RecordSize ] [ [ + [ FSkip ]
[ .CSkip ] [ b ] [ d ] [ f ] [ i ] [ n ] [ r ] ] [ – [ FSkip ] [ .CSkip ] [ b ] [ d ] [ f ]
[ i ] [ n ] [ r ] ] ] [ -k KeyDefinition ] [文档 ]

使用说明sort 命令对 File 参数指定的文档中的行排序,并将结果写到标准输出。假如 File 参数指定多个文档,那么 sort 命令将这些文档连接起来,并当作一个文档进行排序。-(减号)代替文档名指定标准输入。
假如您不指定任何文档名,那么该命令对标准输入排序。能够使用 -o 标志指定输出文档。
假如不指定任何标志,sort 命令基于当前语言环境的整理顺序对输入文档的任何行排序。

主要参数-A 使用 ASCII 整理顺序代替当前语言环境的整理顺序在逐字节的基础上排序。
-b 忽略前导空格和制表符,找出字段的第一或最后列。
-c 检查输入是否已按照标志中指定的排序规则进行排序。假如输入文档排序不正确,就返回一个非零值。
-d 使用字典顺序排序。比较中仅考虑字母、数字和空格。
-f 比较前将任何小写字母改成大写字母。
-i 比较中忽略任何非显示字符。
-k KeyDefinition 指定排序关键字。KeyDefinition 选项的格式为:
[ FStart [ .CStart ] ] [ Modifier ] [ , [ FEnd [ .CEnd ] ][ Modifier ] ]
排序关键字包括任何以 FStart 变量指定的字段和 CStart 变量指定的列开头的字符及以 FEnd 变量指定的字段和CEnd 变量指定的列结束的字符。Modifier 变量的值能够是 b、d、f、i、n 或 r。修饰符和同一字母的标志等价。

-m 只合并多个输入文档;假设输入文档已排序。
-n 按算术值对数字字段排序。数字字段可包含前导空格、可选减号、十进制数字、千分位分隔符和可选基数符。对包含任何非数字字符的字段进行数字排序会出现无法预知的结果。
-o OutFile 将输出指向 OutFile 参数指定的文档,而不是标准输出。OutFile 参数值能够和 File 参数值相同。
-r 颠倒指定排序的顺序。
-t Character 指定 Character 为单一的字段分隔符。
-u 禁止按照排序关键字和选项的任何等同排序(每一组行中一行除外)。
-T Directory 将创建的任何临时文档放入 Directory 参数指定的目录中。
-y[Kilobytes] 用 Kilobytes 参数指定的主存储的千字节数启动 sort 命令,并根据需要增加存储量。
(假如 Kilobytes 参数指定的值小于最小存储站点或大于最大存储站点,就以这个最小存储站点或最大存储站点取代)。
假如省略 -y 标志,sort 命令以缺省的存储大小启动。
-y0 标志用最小存储启动,而 -y 标志(不带 Kilobytes 值)用最大存储启动。sort 命令使用的存储量显著地影响性能。以大存储量对小文档排序将很浪费。
-z RecordSize 假如正在排序的任一行大于缺省的缓冲区大小,要防止出现异常终止。
指定 -c 或 -m 标志时,省略排序阶段,使用系统的缺省缓冲大小。假如已排序行超出这一大小,排序异常终止。
-z 选项指定排序阶段最长行的记录,因而可在合并阶段分配足够的缓冲区。
RecordSize 必须指明等于或大于要合并的最长行的字节值。

应用实例

要在 LC_ALL、LC_COLLATE 或 LANG 环境变量配置为 En_US 的情况下排序 fruits 文档,请输入:

LANG=En_US sort fruits
此命令序列显示以升序词典顺序排序的 fruits 文档的内容。每一列的字符,包括空格、数字和特别字符都经一一比较。
例如,假如 fruits 文档包含文本:

banana
orange
Persimmon
apple
%%banana
apple
ORANGE

sort 命令显示:

%%banana
ORANGE
Persimmon
apple
apple
banana
orange

在 ASCII 整理序列中,%(百分号)在大写字母前,大写字母在小写字母前。
假如您当前的语言环境指定 ASCII 之外的字符集,结果可能不同。

要以字典顺序排序,请输入:

sort  -d fruits

此命令序列排序和显示 fruits 文档的内容,并且只比较字母、数字和空格。
假如 fruits 文档和示例 1 相同,那么 sort 命令显示:

ORANGE
Persimmon
apple
apple
%%banana
banana
orange

-d 标志忽略 %(百分号)字符,因为他不是个字母、数字或空格。(即 %%banana 被 banana 取代)。

要将包含大写字母和具备类似小写行的特别字符行分组,请输入:

sort -d -f fruits

-d 标志忽略特别字符,-f 标志忽略大小写差异。
将 LC_ALL、LC_COLLATE 或 LANG 环境变量配置为 C 的情况下,fruits 文档的输出结果变为:

apple
apple
%%banana
banana
ORANGE
orange
Persimmon

要除去重复行排序,请输入:

sort  -d  -f  -u fruits

-u 标志告诉 sort 命令除去重复的行,使文档中的每一行唯一。此命令序列显示:

apple
%%banana
ORANGE
Persimmon

不但除去重复的 apple,而且也除去了 banana 和 ORANGE。
除去这些是因为 -d 标志忽略 %% 这个特别字符,-f 标志忽略大小写差异。

要如上面那样排序,除去重复的实例(除非是大写字母或标点不同),请输入:

sort  -u +0  -d -f +0 fruits

输入 +0 -d -f 完成的排序和示例 3 中 -d -f 的排序类型相同,+0 进行另一项比较以区分不相同的行。
这防止 -u 标志将他们除去。
示例 1 所示的 fruits 文档中,添加的 +0 将 %%banana 和 banana 及 ORANGE 和 orange 区分开来。
然而,apple 的两个实例是相同的,所以其中之一被删除。

apple
%%banana
banana
ORANGE
orange
Persimmon

要指定分隔字段的字符,请输入:

sort  -t: +1 vegetables

此命令序列排序 vegetables 文档,对每一行上第一个冒号后的文本进行比较。
+1 告诉 sort 命令忽略第一字段,从第二字段的开始到该行的结束进行比较。-t: 标志告诉 sort 命令冒号分隔字段。
假如 vegetables 包含:

yams:104
turnips:8
potatoes:15
carrots:104
green beans:32
radishes:5
lettuce:15

那么,将 LC_ALL、LC_COLLATE 或 LANG 环境变量配置为 C 的情况下,sort 命令将显示:

carrots:104
yams:104
lettuce:15
potatoes:15
green beans:32
radishes:5
turnips:8

注意数字没有按照数字排序。当用字典式分类从左至右比较每一个字符时出现这种情况。
换句话说,3 在 5 之前,所以 32 在 5 之前。

要排序数字,请输入:

sort  -t: +1  -n vegetables

此命令序列按照第二个字段对 vegetables 文档进行数字排序。
假如 vegetables 文档和示例 6 中的相同,那么 sort 命令将显示:

radishes:5
turnips:8
lettuce:15
potatoes:15
green beans:32
carrots:104
yams:104

要对多个字段排序,请输入:

sort  -t: +1 -2  -n +0 -1  -r vegetables
或
sort  -t:  -k2,2 n -k1,1 r vegetables

此命令序列对第二字段(+1 -2 -n)进行数字排序。在这个顺序中,他以逆字母顺序(+0 -1 -r)对第一字段排序。
将 LC_ALL、LC_COLLATE 或 LANG 环境变量配置为 C 的情况下,输出将类似于:

radishes:5
turnips:8
potatoes:15
lettuce:15
green beans:32
yams:104
carrots:104

此命令按数字顺序对行排序。当两行数字相同时,他们以逆字母顺序出现。

要使用排序的文本替换原始文档,请输入:

sort  -o vegetables vegetables

此命令序列将排序输出存入 vegetables 文档( -o vegetables)。

linux sort 命令详解
sort命令的功能是对文件中的各行进行排序。sort命令有许多非常实用的选项,这些选项最初是用来对数据库格式的文件内容进行各种排序操作的。实际上,sort命令可以被认为是一个非常强大的数据管理工具,用来管理内容类似数据库记录的文件。
  Sort命令将逐行对文件中的内容进行排序,如果两行的首字符相同,该命令将继续比较这两行的下一字符,如果还相同,将继续进行比较。
  语法:
  sort [选项] 文件
  说明:sort命令对指定文件中所有的行进行排序,并将结果显示在标准输出上。如不指定输入文件或使用”- “,则表示排序内容来自标准输入。

  sort排序是根据从输入行抽取的一个或多个关键字进行比较来完成的。排序关键字定义了用来排序的最小的字符序列。缺省情况下以整行为关键字按ASCII字符顺序进行排序。

  改变缺省设置的选项主要有:

  - m 若给定文件已排好序,合并文件。

  - c 检查给定文件是否已排好序,如果它们没有都排好序,则打印一个出错信息,并以状态值1退出。

  - u 对排序后认为相同的行只留其中一行。

  - o 输出文件 将排序输出写到输出文件中而不是标准输出,如果输出文件是输入文件之一,sort先将该文件的内容写入一个临时文件,然后再排序和写输出结果。

  改变缺省排序规则的选项主要有:

  - d 按字典顺序排序,比较时仅字母、数字、空格和制表符有意义。

  - f 将小写字母与大写字母同等对待。

  - I 忽略非打印字符。

  - M 作为月份比较:”JAN”<"FEB"   - r 按逆序输出排序结果。   +posl - pos2 指定一个或几个字段作为排序关键字,字段位置从posl开始,到pos2为止(包括posl,不包括pos2)。如不指定pos2,则关键字为从posl到行尾。字段和字符的位置从0开始。   - b 在每行中寻找排序关键字时忽略前导的空白(空格和制表符)。   - t separator 指定字符separator作为字段分隔符。   下面通过几个例子来讲述sort的使用。   用sort命令对text文件中各行排序后输出其结果。请注意,在原文件的第二、三行上的第一个单词完全相同,该命令将从它们的第二个单词vegetables与fruit的首字符处继续进行比较。

  $ cat text
  vegetable soup
  fresh vegetables
  fresh fruit
  lowfat milk

  $ sort text
  fresh fruit
  fresh vegetables
  lowfat milk
  vegetable soup

  用户可以保存排序后的文件内容,或把排序后的文件内容输出至打印机。下例中用户把排序后的文件内容保存到名为result的文件中。

$ sort text>result

  以第2个字段作为排序关键字对文件example的内容进行排序。

$ sort +1-2 example

  对于file1和file2文件内容反向排序,结果放在outfile中,利用第2个字段的第一个字符作为排序关键字。

$ sort -r -o outfile +1.0 -1.1 example


  sort排序常用于在管道中与其他命令连用,组合完成比较复杂的功能,如利用管道将当前工作目录中的文件送给sort进行排序,排序关键字是第6个至第8个字段。
$ ls - l | sort +5 - 7

  sort命令也可以对标准输入进行操作。例如,如果您想把几个文件文本行合并,并对合并后的文本行进行排序,您可以首先用命令cat把多个文件合并,然后用管道操作把合并后的文本行输入给命令sort,sort命令将输出这些合并及排序后的文本行。在下面的例子中,文件veglist与文件fruitlist的文本行经过合并与排序后被保存到文件clist中。

$ cat veglist fruitlist | sort > clist

来自:http://www.opensourceproject.org.cn/article.php?id=331

原文地址 http://www.sudu.cn/info/html/edu/linux/20070101/291747.html

如何乱序排列文件内容

[存] 如何乱序排列文件内容

洗牌问题:洗一副扑克,有什么好办法?既能洗得均匀,又能洗得快?即相对于一个文件来说怎样高效率的实现乱序排列?

ChinaUnix 确实是 Shell 高手云集的地方,只要你想得到的问题,到那里基本上都能找到答案。r2007 给出了一个取巧的方法,利用 Shell 的 $RANDOM 变量给原文件的每一行加上随机的行号然后根据这个随机行号进行排序,再把临时加上去的行号给过滤掉,这样操作之后得到的新文件就相当于被随机“洗”了一次:

while read i;do echo "$i $RANDOM";done

当然如果你的源文件每行的内容比较复杂的话就必须对这段代码进行改写,但只要知道了处理的关键技巧,剩下的问题都不难解决。

另外一篇来自苏蓉蓉的用 awk 来实现洗牌效果的随机文件排序代码分析(原贴在这里,以及对此帖的一个后续讨论,如果你没有登录帐号的话可以到这里查看精华区文章)则写的更为详细:

--------------------------------------------------------------------
关于洗牌问题,其实已经有了一个很好的shell解法,这里另外给三个基于AWK的方法,有错误之处还请不吝指出。

方法一:穷举
类似于穷举法,构造一个散列来记录已经打印行出现行的次数,如果出现次数多于一次则不进行处理,这样可以防止重复,但缺点是加大了系统的开销。

awk -v N=`sed -n '$=' data` '
BEGIN{
FS="\n";
RS=""
}
{
srand();
while(t!=N){
  x=int(N*rand()+1);
  a[x]++;
  if(a[x]==1)
    {
        print $x;t++
    }
  }
}
' data

方法二:变换
基于数组下标变换的办法,即用数组储存每行的内容,通过数组下标的变换交换数组的内容,效率好于方法一。

#! /usr/awk

BEGIN{
srand();
}

{
b[NR]=$0;
}

END{

C(b,NR);
for(x in b)
  {
    print b[x];
  }}

function C(arr,len,i,j,t,x){

for(x in arr)
  {
      i=int(len*rand())+1;
      j=int(len*rand())+1;
      t=arr[i];
      arr[i]=arr[j];
      arr[j]=t;
  }

}

方法三:散列
三个方法中最好的。
利用AWK中散列的特性(详细请看:info gawk 中的7.x ),只要构造一个随机不重复的散列函数即可,因为一个文件每行的linenumber是独一无二的,所以用:

随机数+每行linenumber ------对应------> 那一行的内容

即为所构造的随机函数。
从而有:

awk 'BEGIN{srand()}{b[rand()NR]=$0}END{for(x in b)print b[x]}' data

其实大家担心的使用内存过大的问题不必太在意,可以做一个测试:

测试环境:
PM 1.4GHz CPU,40G硬盘,内存256M的LAPTOP
SUSE 9.3 GNU bash version 3.00.16 GNU Awk 3.1.4

产生一个五十几万行的随机文件,大约有38M:

od /dev/urandom |dd  count=75000 >data

拿效率较低的方法一来说:

洗牌一次所用时间:

time awk -v N=`sed -n '$=' data` '
BEGIN{
FS="\n";
RS=""
}
{
srand();
while(t!=N){
  x=int(N*rand()+1);
  a[x]++;
  if(a[x]==1)
    {
        print $x;t++
    }
  }
}
' data

结果(文件内容省略):
real 3m41.864s
user 0m34.224s
sys 0m2.102s

所以效率还是勉强可以接受的。

方法二的测试:

time awk -f awkfile datafile

结果(文件内容省略):
real 2m26.487s
user 0m7.044s
sys 0m1.371s

效率明显好于第一个。

接着考察一下方法三的效率:

time awk 'BEGIN{srand()}{b[rand()NR]=$0}END{for(x in b)print b[x]}' data

结果(文件内容省略):
real 0m49.195s
user 0m5.318s
sys 0m1.301s

对于一个38M的文件来说已经相当不错了。
--------------------------------------------------------------------

附带存一个来自 flyfly 写的 python 版本乱序代码:

#!/usr/bin/env python
#coding:gb2312
import sys
import random

def usage():
	print "usage:program srcfilename dstfilename"
	
global filename
filename = ""
try:
	filename = sys.argv[1]
except:
	usage()
	
raise()

#open the phonebook file
f = open(filename, 'r')
phonebook = f.readlines()
print phonebook
f.close()

#write to file randomly
try:
	filename = sys.argv[2]
except:
	usage()
	
raise()
f = open(filename, 'w')
random.shuffle(phonebook)
f.writelines(phonebook)
f.close()

shell字符串的截取

shell字符串的截取
Linux shell 截取字符变量的前8位,有方法如下:
1.expr substr “$a” 1 8
2.echo $a|awk ‘{print substr(,1,8)}’
3.echo $a|cut -c1-8
4.echo $
5.expr $a : ‘\(.\\).*’
6.echo $a|dd bs=1 count=8 2>/dev/null
按指定的字符串截取
1、第一种方法:
${varible##*string} 从左向右截取最后一个string后的字符串
${varible#*string}从左向右截取第一个string后的字符串
${varible%%string*}从右向左截取最后一个string后的字符串
${varible%string*}从右向左截取第一个string后的字符串
“*”只是一个通配符可以不要
例子:
$ MYVAR=foodforthought.jpg
$ echo ${MYVAR##*fo}
rthought.jpg
$ echo ${MYVAR#*fo}
odforthought.jpg
2、第二种方法:${varible:n1:n2}:截取变量varible从n1到n2之间的字符串。
可以根据特定字符偏移和长度,使用另一种形式的变量扩展,来选择特定子字符串。试着在 bash 中输入以下行:
$ EXCLAIM=cowabunga
$ echo ${EXCLAIM:0:3}
cow
$ echo ${EXCLAIM:3:7}
abunga
这种形式的字符串截断非常简便,只需用冒号分开来指定起始字符和子字符串长度。
按照指定要求分割:
比如获取后缀名
ls -al | cut -d “.” -f2

看日记学git

《看日记学git》linux大棚
git常用操作
1、
获得帮助可以使用类似man git-****的命令格式:
想获得关于commit命令的帮助,则man git-commit
想获得关于pull命令的帮助,则man git-pull
想获得关于merge命令的帮助,则man git-merge
以此类推
2、
任何人在使用git之前,都要提交简单的个人信息,以便git区分不同的提交者身份。

#git config –global user.name “your name”
#git config –global user.email yourname@example.com

3、
想新开启一个项目,应该先建立一个目录,例如名为myproject,然后所有的项目开发内容都在此目录下进行。

#cd myproject
#git init
#git add .
#git commit //这个步骤会自动进入编辑状态,要求提交者输入有关本次提交的“开发信息”

至此,一个新项目就诞生了,第一个开发信息(开发日志)也随之诞生。
4、
如果改进了项目源代码,并且到了开发者认为“应该再次记录开发信息”的时候,则提交“工作成果”。
#git commit -a //这是一个偷懒的命令,相当于git add .; git commit;
但是,此处有一点应该注意,那就是git commit -a无法把新增文件或文件夹加入进来,所以,如果你新增了文件或文件夹,那么就要老老实实的先git add .,再git commit喽。[此处非常感谢freeren的提醒]
5、
想检查到目前为止对源码都做了哪些修改(相对于本次工作刚开始之时):

#git diff //这个命令只在git add之前使用有效。如果已经add了,那么此命令输出为空
#git diff –cached //这个命令在git add之后在git commit之前有效。
#git status  //这个命令在git commit之前有效,表示都有哪些文件发生了改动

6、
想查看自项目开启到现在的所有开发日志

#git log
#git log -p //会输出非常详细的日志内容,包括了每次都做了哪些源码的修改

7、
开启一个试验分支(experimental),如果分支开发成功则合并到主分支(master),否则放弃该试验分支。

#git branch experimental //创建一个试验分支,名称叫experimental
#git branch //显示当前都有哪些分支,其中标注*为当前所在分支
#git checkout experimental //转移到experimental分支
(省略数小时在此分支上的开发过程)…

如果分支开发成功:

#git commit -a //在experimental分支改进完代码之后用commit在此分支中进行提交
#git checkout master //转移回master分支
#git merge experimental //经证实分支开发成功,将exerimental分支合并到主分支
#git commit -a //彻底完成此次分支合并,即提交master分支
#git branch -d experimental //因为experimental分支已提交,所以可安全删除此分支

如果分支开发失败:

#git checkout master
#git branch -D experimental //由于分支被证明失败,因此使用-D来放弃并删除该分支

8、
随时查看图形化分支信息。

#gitk

9、
当合作伙伴bob希望改进我(rocrocket)的工作成果,则:

bob$git clone /home/rocrocket/project myrepo //此命令用于克隆我的工作到bob的myrepo目录下。请注意,此命令有可能会因为/home/rocrocket的目录权限问题而被拒绝,解决方法是chmod o+rx /home/rocrocket。
(省略bob数小时的开发过程)…
bob$git commit -a //bob提交自己的改进成果到自己的git仓库中,并口头告知我(rocrocket)他已经完成了工作。

我如果非常非常信任bob的开发能力:

$cd /home/rocrocket/project
$git pull /home/bob/myrepo //pull命令的意思是从远端git仓库中取出(git-fetch)修改的代码,然后合并(git-merge)到我(rocrocket)的项目中去。读者要记住一个小技巧,那就是“git pull .”命令,它和git merge的功能是一样的,以后完全可以用git pull .来代替git merge哦!请注意,git-pull命令有可能会因为/home/bob的目录权限问题而被拒绝,解决方法是chmod o+rx /home/bob。

如果我不是很信任bob的开发能力:

$cd /home/rocrocket/project
$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分支合并到我的项目中了
$git branch -D bobworks //如果我检查了bob的工作后很不满意,就可以用-D来放弃这个分支就可以了
过了几天,bob如果想继续帮助我开发,他需要先同步一下我这几天的工作成果,只要在其当初clone的myrepo目录下执行git pull即可:
#git pull //不用加任何参数,因为当初clone的时候,git已经记住了我(rocrocket)的工作目录,它会直接找到我的目录来取。

git历史记录操作
1、
git的历史记录是由一些列相关联的”commit”所组成的。每一次“commit”都会有一个唯一的名称。如下黑体字所示:

[rocrocket@wupengchong project]$ git log
commit 5b888402aadd3cd41b3fe8c84a8658da07893b20
Author: rocrocket 
Date:   Wed Sep 24 13:16:46 2008 +0800

after pull from rocrocket

Hello!!!!!

2、
我们可以使用git show再加上述的commit名称来显式更详细的commit信息:

[rocrocket@wupengchong project]$ git show 5b888402aadd3cd41b3fe8c84a8658da07893b20

你完全可以用一个最短的且唯一的“名称前几个字符”来只待某次commit:

[rocrocket@wupengchong project]$ git show 5b888 //只要能区别与其他名称就足够了

使用git show加分支名称,亦可以显示分支信息:

[rocrocket@wupengchong project]$git show master
[rocrocket@wupengchong project]$git show experimental

使用HEAD字段可以代表当前分支的头(也就是最近一次commit):

[rocrocket@wupengchong project]$git show HEAD

每一次commit都会有”parent commit”,可以使用^表示parent:

[rocrocket@wupengchong project]$git show HEAD^ //查看HEAD的父母的信息
[rocrocket@wupengchong project]$git show HEAD^^ //查看HEAD的父母的父母的信息
[rocrocket@wupengchong project]$git show HEAD~4 //查看HEAD上溯4代的信息

要注意的是git-merge是会产生双父母的,这种情况这样处理:

[rocrocket@wupengchong project]$git show HEAD^1 //查看HEAD的第一个父母
[rocrocket@wupengchong project]$git show HEAD^2 //查看HEAD的第二个父母

3、
你可以给复杂名称起个别名:

[rocrocket@wupengchong project]$ git tag V3 5b888 //以后可以用V3来代替复杂的名称(5b888…)
[rocrocket@wupengchong project]$ git show V3
[rocrocket@wupengchong project]$ git branch stable V3 //建立一个基于V3的分支

4、
可以用git grep帮助我们搜索:

[rocrocket@wupengchong project]$ git grep “print” V3 //在V3中搜索所有的包含print的行
[rocrocket@wupengchong project]$ git grep “print” //在所有的历史记录中搜索包含print的行

5、
定位具体的历史记录

[rocrocket@wupengchong project]$ git log V3..V7 //显示V3之后直至V7的所有历史记录
[rocrocket@wupengchong project]$ git log V3.. //显示所有V3之后的历史记录。注意..中任何一个被省略都将被默认设置为HEAD。所以如果使用..的话,git log在大部分情况下会输出空的。
[rocrocket@wupengchong project]$ git log –since=”2 weeks ago” //显示2周前到现在的所有历史记录。具体语法可查询git-ref-parse命令的帮助文件。
[rocrocket@wupengchong project]$ git log stable..experimental //将显示在experimental分支但不在stable分支的历史记录
[rocrocket@wupengchong project]$ git log experimental..stable //将显示在stable分支但不在experimental分支的历史记录

6、
你最喜欢的gitk也可以定位具体的历史记录:

[rocrocket@wupengchong project]$ gitk –since=”2 weeks ago” drivers/ //将在GUI中显示自2周前到现在为止的且位于drivers目录下的分支记录信息

到目前为止,我们针对最基本的git的操作已经学习完毕了。经过了这么多天的“看日记”,想必大家对这种学习形式有自己的认识和评价,希望大家写在下面的留言区,我会不断改进,为大家奉献更好的专题系列。:)

git索引文件(index file)的作用。
我们在提交工作时,使用最多的命令就是git commit -a了,但是这个将提交你所做的所有工作。其实,如果你了解commit的工作机制,你会知道我们可以自定义提交哪些部分到哪些工作树中,其实自由度很大的。

1、
还记得之前我们建立的test-project工作目录么。我们继续在这个目录下演示讲解。

[rocrocket@wupengchong test-project]$ echo “hello world,again”>>file.txt

这次,我们不急着执行commit命令,而是先用git diff看看差别情况:

[rocrocket@wupengchong test-project]$ git diff
diff –git a/file.txt b/file.txt
index 60be00d..a559610 100644
— a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
Hi,rocrocket!
+hello world,again

好了,我们可以看到git报告了我们刚才所做的修改。下面我们来add一下,然后再git diff,看看diff有什么变化呢:

[rocrocket@wupengchong test-project]$ git add file.txt
[rocrocket@wupengchong test-project]$ git diff
[rocrocket@wupengchong test-project]$

大家可以看到在add之后的git diff的输出竟然为空了,但是此时我们尚未执行commit阿。如果这个时候你执行git diff HEAD,你仍然会看到修改报告:

[rocrocket@wupengchong test-project]$ git diff HEAD
diff –git a/file.txt b/file.txt
index 60be00d..a559610 100644
— a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
Hi,rocrocket!
+hello world,again

这就说明了一个问题:git diff不是在和HEAD比,而是和另外一个“神秘”内容在比,而这个“神秘”内容就是“索引文件”!
索引文件(index file)就是.git/index文件,它是二进制形式的文件。我们可以用ls-files来检查它的内容。

[rocrocket@wupengchong test-project]$ git ls-files –stage  //一定要记住,此命令是用于查看index file的!!
100644 a55961026a22bdd4e938dcc90a4a83823a81f776 0       file.txt
[rocrocket@wupengchong test-project]$ git cat-file -t a5596
blob
[rocrocket@wupengchong test-project]$ git cat-file blob a5596
Hi,rocrocket!
hello world,again

很明显,我们可以看到其内容已经是改进后的代码了,怪不得git-diff会输出空呢!
我们的结论就是git add的作用就是创建一个blob文件来记录最新的修改代码,并且在index file里添加一个到此blob的链接。
2、
如果在git-diff后面加上参数HEAD,则git-diff会显示当前工作目录和最近一次提交之间的代码区别。

[rocrocket@wupengchong test-project]$ echo ‘again?’>>file.txt
[rocrocket@wupengchong test-project]$ git diff HEAD
diff –git a/file.txt b/file.txt
index 60be00d..dfb67dc 100644
— a/file.txt
+++ b/file.txt
@@ -1 +1,3 @@
Hi,rocrocket!
+hello world,again
+again?

如果使用参数–cached,则会比较index file和最近一次提交之间的代码区别。

[rocrocket@wupengchong test-project]$ git diff –cached
diff –git a/file.txt b/file.txt
index 60be00d..a559610 100644
— a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
Hi,rocrocket!
+hello world,again

按我的认识,更清楚且通俗的解释就是:git维护的代码分成三部分,“当前工作目录”<->“index file”<->git仓库。git commit会将index file中的改变写到git仓库;git add会将“当前工作目录”的改变写到“index file”;“commit -a”则会直接将“当前工作目录”的改动同时写到“index file”和“git仓库”。

而git diff的使用稍微有些复杂,大家可以看看Lee.MaRS对于这个命令非常精彩的分析(蓝色字部分):(在此非常感谢Lee.MaRS)

将 Current working directory 记为 (1)
将 Index file 记为 (2)
将 Git repository 记为 (3)

他们之间的提交层次关系是 (1) -> (2) -> (3)
git add完成的是(1) -> (2)
git commit完成的是(2) -> (3)
git commit -a两者的直接结合

从时间上看,可以认为(1)是最新的代码,(2)比较旧,(3)更旧
按时间排序就是 (1) <- (2) <- (3)

git diff得到的是从(2)到(1)的变化
git diff –cached得到的是从(3)到(2)的变化
git diff HEAD得到的是从(3)到(1)的变化

3、
status命令会显示当前状态的一个简单总结:

[rocrocket@wupengchong test-project]$ git status
# On branch master
# Changes to be committed:
#   (use “git reset HEAD …” to unstage)
#
#    modified:   file.txt
#
# Changed but not updated:
#   (use “git add …” to update what will be committed)
#
#    modified:   file.txt
#

上面两行黑体字中的Changes to be committed表示在index和commit的区别状况。而Changed but not updated表示当前目录和index的区别状况。

git-clone命令,主要负责克隆git仓库到另一个新目录中。

我们可以用如下的表示法来定义远程的一个git仓库:

rsync://host.xz/path/to/repo.git/
http://host.xz/path/to/repo.git/
https://host.xz/path/to/repo.git/
git://host.xz/path/to/repo.git/
git://host.xz/~user/path/to/repo.git/
ssh://[user@]host.xz[:port]/path/to/repo.git/
ssh://[user@]host.xz/path/to/repo.git/
ssh://[user@]host.xz/~user/path/to/repo.git/
ssh://[user@]host.xz/~/path/to/repo.git

如果git仓库在本地,那么可以这样表示:

/path/to/repo.git/
file:///path/to/repo.git/

本次我们将从一个独立开发者的视角来学习git。
1、
独立开发者的最大特点就是他们不需要和其他人来交换补丁,而且只在一个独立的固定的git仓库中工作。
下面这些命令将可以帮助你完成日常工作:

git-show-branch:可以显示你当前所在的分支以及提交记录。
git-log:显示提交日志
git-checkout或者git-branch:用于切换和创建分支
git-add:用于将修改内容加入到index文件中
git-diff和git-status:用于显示开发者所做的修改
git-commit:用于提交当前修改到git仓库。
git-reset和git-checkout:用于撤销某些修改
git-merge:用于合并两个分支
git-rebase:用于维护topic分支(此处我也不太懂,等完成git学习后转过头来会关注此问题)
git-tag:用于标记标签。

2、
我们来举一个例子,模拟一下独立开发者使用git的情形。
首先使用一个tarball作为整个项目的初始点。

$ tar -xzvf mypro.tat.gz
$ cd  mypro
$ git-init
$ git add .
$ git commit -m “important of mypro source tree.”
$ git tag v2.43 //给这个commit起了一个简单的名字v2.43

下面我们建立分支并继续开发:

$ git checkout -b alsa-audio //-b用于建立一个新的分支,分支名称为alsa-audio,并且转移到此分支
…(开发/编译/测试)
$ git checkout — curses/ux_audio_oss.c //用于取消对curses/ux_audio_oss.c文件的修改
$ git add curses/ux_audio_alsa.c //如果你在这一阶段的开发过程中增加了新文件,那么你应该用git-add告知git仓库,当然,如果你只是修改或删除,那么使用git-commit -a就可以让git查觉到了。
…(开发/编译/测试)
$ git diff HEAD //查看一下我们即将commit的内容
$ git commit -a -s //提交
…(开发/编译/测试)
$ git reset –soft HEAD^ //回复到上一次commit的代码。–soft选项表示不改动index file和working tree中的内容
…(开发/编译/测试)
$ git diff ORIG_HEAD //look at the changes since the premature commit we took back(此句不太懂)
$ git commit -a -c ORIG_HEAD //重新提交,-c ORIG_HEAD表示使用原有的提交信息
$ git checkout master
$ git merge alsa-audio
$ git log –since=’3 days ago’
$ git log v2.43.. curses/    //查看自从v2.43以来的curses目录下的代码变化信息

上次是从一个独立开发者的视角来学习git,这次要从一个合作开发者的角度来看问题,这个更加实用。
1、
作为项目开发者的一员,学会和队友交流是一件很重要的事情。因此,我们不仅要掌握独立开发者所掌握的命令,还要掌握用于沟通的git命令。

git-clone:复制别人的git仓库到本地
git-pull和git-fetch:保持和别人的git仓库的同步更新
git-push:共享方式。等同于CVS方式下的共享。
git-format-patch:利用邮件形式提交补丁。等同于内核开发方式。

2、
情景模拟:

$git clone git://git.kernel.org/pub/scm/…/torvalds/linux-2.6 my2.6
$cd my2.6
(开发…编译…测试…)
$git commit -a -s //-s选项用于在commit信息后加上结束标志
$git format-patch origin //从本地分支生成patch,用于email提交
$git pull //从origin取出更新并合并到当前分支
$git log -p ORIG_HEAD.. arch/i386 include/asm-i386
$git pull git://git.kernel.org/pub/…/jgarzik/libata-dev.git ALL //从特定git仓库取出变更并合并。
$git reset –hard ORIG_HEAD //恢复到上一次的内容
$git gc //用垃圾回收机制清除由于reset而造成的垃圾代码
$git fetch –tags //从origin取出tags并存储到.git/refs/tags

3、
学习到这里,一定会感觉有些迷糊了,原因是对于git的各种命令还不是很熟悉,所以无法和实际应用相结合。
计划在下次进行一次大总结。之后再进入“Integator和admin的讲解”。:)

这篇文章将总结之前文章的知识点,
但并不会地毯式总结,简单的命令在这里不会重复,
我们总结的将是疑难知识点。
我提炼出来的需要解决的疑难问题包括:

1 commit和commit -a的区别
2 log -p的中-p的作用
3 merge的用法及参数用法
4 fetch的用法
5 pull的用法
6 commit信息详解
7 HEAD的含义及相关用法,ORIG_HEAD的用法以及其他常量的使用方法
8 tag的用法

下面我们就一一总结上述问题。
1、
commit和commit -a的区别
commit -a相当于:
第一步:自动地add所有改动的代码,使得所有的开发代码都列于index file中
第二步:自动地删除那些在index file中但不在工作树中的文件
第三步:执行commit命令来提交
2、
log -p的中-p的作用
git log:显示commit日志
git log -p:不仅显示commit日志,而且同时显示每次commit的代码改变。
3、
merge的用法及参数用法
git-merge主要用于将两个或两个以上的开发分支进行合并。
git merge branchname 用于将branchname分支合并到当前分支中。(如果合并发生冲突,需要自己解决冲突)
当merge命令自身无法解决冲突的时候,它会将工作树置于一种特殊的状态,并且给用户提供冲突信息,以期用户可以自己解决这些问题。当然在这个时候,未发生冲突的代码已经被git merge登记在了index file里了。如果你这个时候使用git diff,显示出来的只是发生冲突的代码信息。
在你解决了冲突之前,发生冲突的文件会一直在index file中被标记出来。这个时候,如果你使用git commit提交的话,git会提示:filename.txt needs merge
在发生冲突的时候,如果你使用git status命令,那么会显示出发生冲突的具体信息。
在你解决了冲突之后,你可以使用如下步骤来提交:

第一步:git add filename.txt
第二步:git commit

如果你希望撤销一个分支到merge前的状态,那么使用如下命令:

$ git reset –hard HEAD //–hard表示将working tree和index file都撤销到以前状态

在这先偷偷的告诉你,–soft表示只撤销commit,而保留working tree和index file的信息,–mixed会撤销commit和index file,只保留working tree的信息。OK,如果你能记住–hard、–mixed和–soft的区别,那最好,如果记不住,也不用自责啦,以后还会讲到。
4、
fetch的用法
git-fetch用于从另一个reposoitory下载objects和refs。
命令格式为:git fetch
其中表示远端的仓库路径。
其中的标准格式应该为:表示源的分支,如果不为空,则表示本地的分支;如果为空,则使用当前分支。
git fetch /home/bob/myrepo master:bobworks :用于从bob的工作目录的master分支下载objects和refs到本地的bobworks分支中。
5、
pull的用法
git-pull的作用就是从一个repository取出内容并合并到另一个repository中。
git pull是git fetch和git merge命令的一个组合。
git pull /home/bob/myrepo 这个命令的意思是从此目录中取出内容并合并到当前分支中。
git pull .就相当于git merge。
6、
commit信息详解
你使用git log可以看到每一次commit的信息,大约如下格式:

[rocrocket@wupengchong project]$ git log
commit 5b888402aadd3cd41b3fe8c84a8658da07893b20
Author: rocrocket 
Date:   Wed Sep 24 13:16:46 2008 +0800

after pull from rocrocket

Hello!!!!!

可以看到黑体部分为本次commit的ID号,你可以根据这个号码,使用git show来显示这次commit的更详细的信息,包括了提交时间、修改内容、git diff信息等等。
7、
常量的使用方法

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时的提交信息。

8、
tag的用法
主要作用是给某次commit起一个好记的名字:

[rocrocket@wupengchong project]$ git tag V3 5b888 //以后可以用V3来代替复杂的名称(5b888…)
[rocrocket@wupengchong project]$ git show V3

ps:我敢肯定,读者一定还有很多迷惑之处,但是到目前为止,作为一个初级用户,你简单地使用git已经没有任何问题了。我们如果想继续提高,需要的是对基本命令的更加熟练使用,以及对git原理的了解和把握。

今天,我们重点来探讨一下git-diff的用法。
我们知道在git提交环节,存在三大部分:working tree, index file, commit

这三大部分中:

working tree就是你所工作在的目录,每当你在代码中进行了修改,working tree的状态就改变了。
index file是索引文件,它是连接working tree和commit的桥梁,每当我们使用git-add命令来登记后,index file的内容就改变了,此时index file就和working tree同步了。
commit是最后的阶段,只有commit了,我们的代码才真正进入了git仓库。我们使用git-commit就是将index file里的内容提交到commit中。

总结一下:

git diff是查看working tree与index file的差别的。
git diff –cached是查看index file与commit的差别的。
git diff HEAD是查看working tree和commit的差别的。(你一定没有忘记,HEAD代表的是最近的一次commit的信息)

为了更加清晰的阐释这个关系,我们来给出一个实例。
首先在目录test-git下建立一个c文件,内容如下:
[rocrocket@wupengchong test-git]$ cat main.c

#include
int main(int argc,char *argv[])
{
	printf(“hello.\n”);
	printf(“he was a student.\n”);
	return 0;
}

然后git init, git add . , git commit;
之后你将源代码修改为:
[rocrocket@wupengchong test-git]$ cat main.c

#include
int main(int argc,char *argv[])
{
	printf(“hello.\n”);
	printf(“he was a student.\n”);
	printf(“he was born in finland.\n”);
	return 0;
}

此时你git add .,但不用执行git commit命令。

然后你再将源代码改为:
[rocrocket@wupengchong test-git]$ cat main.c

#include
int main(int argc,char *argv[])
{
	printf(“hello.\n”);
	printf(“he was a student.\n”);
	printf(“he was born in finland.\n”);
	printf(“he is very clever!\n”);
	return 0;
}

这个时候,你执行如下三个命令,仔细查看,我相信你会发现它们三个的区别的!

$ git diff
$ git diff –cached
$ git diff HEAD
	

讲到这里,你基本上对git diff命令有了比较深入的了解了,现在你再使用git status看看输出结果,样子大概是这样:

[rocrocket@wupengchong test-git]$ git status
# On branch master
# Changes to be committed:
#   (use “git reset HEAD …” to unstage)
#
#    modified:   main.c
#
# Changed but not updated:
#   (use “git add …” to update what will be committed)
#
#    modified:   main.c
#

很明显可以知道:
Changes to be committed表示已经存在于index file里,但尚未提交。
Changed but not updated表示在working tree已经做修改,但还没有使用git add登记到index file里。

今天,我们细细研究git branch和git checkout branchname这两个命令。
我们都知道git branch用于显示分支列表并且标识当前所在分支。这个命令比较简单。
git branch branchname 用于建立一个名字叫branchname的分支。但是你想过这个分支会根据什么来建么?是根据working tree?还是根据index file?还是commit呢?(不卖关子,答案告诉你,是…commit!)
下面给大家来个情景重现,来证实一下:git branch branchname 就是根据commit来建立的新分支。

首先在目录test-git下建立一个c文件,内容如下:

[rocrocket@wupengchong test-git]$ cat main.c
#include
int main(int argc,char *argv[])
{
	printf(“hello.\n”);
	printf(“he was a student.\n”);
	return 0;
}

然后git init, git add . , git commit;
之后你将源代码修改为:
[rocrocket@wupengchong test-git]$ cat main.c

#include
int main(int argc,char *argv[])
{
	printf(“hello.\n”);
	printf(“he was a student.\n”);
	printf(“he was born in finland.\n”);
	return 0;
}

此时你git add .,但不用执行git commit命令。
然后你再将源代码改为:
[rocrocket@wupengchong test-git]$ cat main.c

#include
int main(int argc,char *argv[])
{
	printf(“hello.\n”);
	printf(“he was a student.\n”);
	printf(“he was born in finland.\n”);
	printf(“he is very clever!\n”);
	return 0;
}

好了,这个时候,我们就营造了一个环境,在这个git仓库里,working tree、index file和commit都是不同的。(可以用《看日记学git》之十九中介绍的方法来证实这一点。)
此时,我们运行创建分支的命令:

[rocrocket@wupengchong test-git]$ git branch experimental

此命令用来创建一个experimental分支,然后我们用git checkout experimental转移到新分支中。
这时,你使用cat main.c可以发现,其内容是:

[rocrocket@wupengchong test-git]$ cat main.c
#include
int main(int argc,char *argv[])
{
	printf(“hello.\n”);
	printf(“he was a student.\n”);
	return 0;
}

看!这个内容显然是master分支中的commit里的。
OK,今天就强调这一个概念:)记住哦,以后很有用的。
ps:如果你学有余力,我再告诉你一个信息。在你git branch一个新分支后,在目录.git/refs/heads目录下会多出一个新的文件,对应于新分支的名称,用来记录新分支下对应的“最后一次commit的信息”。
ps:如果你学有余力,我还要告诉你一个信息。当你git branch一个新分支并checkout转移到这个新分支后,.git目录下的HEAD文件会相应的改变,它的内容将对应着新分支下“最后一次commit的信息”。

我们已经完全掌握了建立新分支的方法,但是建立了新分支,总要利用起来吧,通俗点说,就是“领导要管得住自己的下属”。
恩,读完本文,你将完全掌握如何利用别人的分支的啦!
我们会以实例的方式,来将分支技术娓娓道来:D 请大家跟上思路。
首先,先概括说一下整体思路:
1 创建一个主分支,名称当然是默认的master,写一个小程序roc.c。
2 之后,创建新分支,命名为wukong(呵呵,你没看错,就是悟空),在wukong分支改进小程序roc.c代码并commit。
3 在wukong的基础上建立bajie(对,八戒),再改进代码roc.c,并在bajie分支commit。
4 切换到wukong,悟空修改自己的代码,并故意造成和bajie的代码冲突。
5 由悟空来操作,将bajie合并到wukong分支。(需要解决代码冲突)
6 由我操作,将wukong合并到master分支。
7 实验完毕。可以删除两个分支了。

好了,大体思路就这么简单,也基本上可以涵盖所有的分支操作情况了。下面,我们就一步一步来开始实例体验吧!
第一步:创建一个主分支,名称当然是默认的master,写一个小程序。

[rocrocket@wupengchong ~]$ mkdir git21
[rocrocket@wupengchong ~]$ cd git21/
[rocrocket@wupengchong git21]$ ls
[rocrocket@wupengchong git21]$ vi roc.c
[rocrocket@wupengchong git21]$ ls
roc.c
[rocrocket@wupengchong git21]$ cat -n roc.c
1  #include
2  int main()
3  {
4          printf(“Please guess who he is.\n”);
5          return 0;
6  }
[rocrocket@wupengchong git21]$

然后进行初始化工作:

[rocrocket@wupengchong git21]$ git init
Initialized empty Git repository in .git/
[rocrocket@wupengchong git21]$ git add *
[rocrocket@wupengchong git21]$ git commit
Created initial commit d5d44dc: master:001
1 files changed, 6 insertions(+), 0 deletions(-)
create mode 100644 roc.c
[rocrocket@wupengchong git21]$

最后是例行检查:

[rocrocket@wupengchong git21]$ git diff
[rocrocket@wupengchong git21]$ git diff –cached
[rocrocket@wupengchong git21]$ git diff HEAD
[rocrocket@wupengchong git21]$ git log
commit d5d44dcdc938a263bdbc1c9cb06de24c27adebc7
Author: rocrocket 
Date:   Thu Oct 9 18:50:39 2008 +0800

master:001
[rocrocket@wupengchong git21]$ git status
# On branch master
nothing to commit (working directory clean)
[rocrocket@wupengchong git21]$ git branch
* master
[rocrocket@wupengchong git21]$

第二步:之后,创建新分支,命名为wukong(你没看错,就是孙猴子,呵呵),在wukong分支改进小程序roc.c代码并commit

[rocrocket@wupengchong git21]$ git branch wukong
[rocrocket@wupengchong git21]$ git branch
* master
wukong
[rocrocket@wupengchong git21]$ git checkout wukong
Switched to branch “wukong”
[rocrocket@wupengchong git21]$ git branch
master
* wukong
[rocrocket@wupengchong git21]$ cat .git/HEAD
ref: refs/heads/wukong
[rocrocket@wupengchong git21]$ ls
roc.c
[rocrocket@wupengchong git21]$ vi roc.c

编辑后roc.c的代码如下:

[rocrocket@wupengchong git21]$ cat -n roc.c
1  #include
2  int main()
3  {
4          printf(“Please guess who he is.\n”);
5          printf(“He is a youngster.\n”);
6          return 0;
7  }
[rocrocket@wupengchong git21]$

下面进行add和例行检查:

[rocrocket@wupengchong git21]$ git add roc.c
[rocrocket@wupengchong git21]$ git diff
[rocrocket@wupengchong git21]$ git diff –cached
diff –git a/roc.c b/roc.c
index 66cb6f0..2fe0f42 100644
— a/roc.c
+++ b/roc.c
@@ -2,5 +2,6 @@
int main()
{
printf(“Please guess who he is.\n”);
+       printf(“He is a youngster.\n”);
return 0;
}
[rocrocket@wupengchong git21]$

下面在wukong分支提交此修改:

[rocrocket@wupengchong git21]$ git commit
Created commit 1ac0798: wukong:001
1 files changed, 1 insertions(+), 0 deletions(-)
[rocrocket@wupengchong git21]$ git log
commit 1ac0798784828577ecb808b03165facfb5bef3e3
Author: rocrocket 
Date:   Thu Oct 9 19:07:57 2008 +0800

wukong:001

commit d5d44dcdc938a263bdbc1c9cb06de24c27adebc7
Author: rocrocket 
Date:   Thu Oct 9 18:50:39 2008 +0800

master:001
[rocrocket@wupengchong git21]$

好了,到此时,我们来查看一下master主分支和wukong分支的代码分别是什么情况:

[rocrocket@wupengchong git21]$ git branch
master
* wukong
[rocrocket@wupengchong git21]$ cat roc.c
#include
int main()
{
	printf(“Please guess who he is.\n”);
	printf(“He is a youngster.\n”);
	return 0;
}
[rocrocket@wupengchong git21]$ git checkout master
Switched to branch “master”
[rocrocket@wupengchong git21]$ git branch
* master
wukong
[rocrocket@wupengchong git21]$ cat roc.c
#include
int main()
{
	printf(“Please guess who he is.\n”);
	return 0;
}
[rocrocket@wupengchong git21]$

显而易见,此时两个分支的代码是不一样的。:)继续我们的实例体验。

第三步、在wukong的基础上建立bajie(对,八戒),再改进代码roc.c,并在bajie分支commit。

[rocrocket@wupengchong git21]$ git checkout wukong
Switched to branch “wukong”
[rocrocket@wupengchong git21]$ git branch
master
* wukong
[rocrocket@wupengchong git21]$ git branch bajie
[rocrocket@wupengchong git21]$ git branch
bajie
master
* wukong
[rocrocket@wupengchong git21]$ git checkout bajie
Switched to branch “bajie”
[rocrocket@wupengchong git21]$ git branch
* bajie
master
wukong
[rocrocket@wupengchong git21]$ vi roc.c

悟空成功建立了bajie分支,并checkout到bajie分支。
然后就是编辑bajie分支的roc.c了:

[rocrocket@wupengchong git21]$ cat -n roc.c
1  #include
2  int main()
3  {
4          printf(“Please guess who he is.\n”);
5          printf(“He is a youngster.\n”);
6          printf(“He is a male.\n”);
7          return 0;
8  }
[rocrocket@wupengchong git21]$

可以看到黑体字,我们加了一句话:He is a male.(他是一个男性)。
好,八戒编辑完代码,可以提交了!

[rocrocket@wupengchong git21]$ git diff
diff –git a/roc.c b/roc.c
index 2fe0f42..05aaf78 100644
— a/roc.c
+++ b/roc.c
@@ -3,5 +3,6 @@ int main()
{
printf(“Please guess who he is.\n”);
printf(“He is a youngster.\n”);
+       printf(“He is a male.\n”);
return 0;
}
[rocrocket@wupengchong git21]$ git add roc.c
[rocrocket@wupengchong git21]$ git commit
Created commit 5a0f9ee: bajie:001
1 files changed, 1 insertions(+), 0 deletions(-)
[rocrocket@wupengchong git21]$ git log
commit 5a0f9ee71fe9ffe259b9b6cf842dd69baa95b307
Author: rocrocket 
Date:   Thu Oct 9 19:16:31 2008 +0800

bajie:001

commit 1ac0798784828577ecb808b03165facfb5bef3e3
Author: rocrocket 
Date:   Thu Oct 9 19:07:57 2008 +0800

wukong:001

commit d5d44dcdc938a263bdbc1c9cb06de24c27adebc7
Author: rocrocket 
Date:   Thu Oct 9 18:50:39 2008 +0800

master:001
[rocrocket@wupengchong git21]$ git branch
* bajie
master
wukong
[rocrocket@wupengchong git21]$

好了,已经在bajie分支提交成功了。(你应该注意到了,对于分支操作来说,不仅代码是向下继承,连log也是向下继承的。不过,log和代码一样是不会自动向上复制的,只有merge了之后才可以看到下层分支的Log和代码)

第四步、切换到wukong,悟空修改自己的代码,并故意造成和bajie的代码冲突。

[rocrocket@wupengchong git21]$ git branch
* bajie
master
wukong
[rocrocket@wupengchong git21]$ git checkout wukong
Switched to branch “wukong”
[rocrocket@wupengchong git21]$ git branch
bajie
master
* wukong
[rocrocket@wupengchong git21]$ vi roc.c

好了,成功切换到wukong,开始在roc.c中加入代码吧!

[rocrocket@wupengchong git21]$ cat -n roc.c
1  #include
2  int main()
3  {
4          printf(“Please guess who he is.\n”);
5          printf(“He is a youngster.\n”);
6          printf(“He is a female.\n”);
7          return 0;
8  }
[rocrocket@wupengchong git21]$

可以看到,呵呵,wukong写了一句He is a female. 很明显,这句话是捣乱的,也就是要故意引起冲突。因为在bajie分支里已经有He is a male.这句话了。好,下面好戏来了,看看怎么解决这个冲突吧!

朋友要注意一点:要合并两个分支的话,两个分支都必须是commit的状态。否则merge可会报错的哦!来,看看merge报错时的样子:

[rocrocket@wupengchong git21]$ git merge bajie
Updating 1ac0798..5a0f9ee
roc.c: needs update
error: Entry ‘roc.c’ not uptodate. Cannot merge.

就是这样,告诉你roc.c需要升级,不能merge。
下面,来将wukong修改的内容commit吧!

[rocrocket@wupengchong git21]$ git diff
diff –git a/roc.c b/roc.c
index 2fe0f42..b8066c1 100644
— a/roc.c
+++ b/roc.c
@@ -3,5 +3,6 @@ int main()
{
printf(“Please guess who he is.\n”);
printf(“He is a youngster.\n”);
+       printf(“He is a female.\n”);
return 0;
}
[rocrocket@wupengchong git21]$ git commit -a
Created commit 4f6ba4e: wukong:002
1 files changed, 1 insertions(+), 0 deletions(-)
[rocrocket@wupengchong git21]$ git log
commit 4f6ba4e96db6110405f615a5ea5d3119faf64d45
Author: rocrocket 
Date:   Thu Oct 9 23:35:31 2008 +0800

wukong:002

commit 1ac0798784828577ecb808b03165facfb5bef3e3
Author: rocrocket 
Date:   Thu Oct 9 19:07:57 2008 +0800

wukong:001

commit d5d44dcdc938a263bdbc1c9cb06de24c27adebc7
Author: rocrocket 
Date:   Thu Oct 9 18:50:39 2008 +0800

master:001

第五步、由悟空来操作,将bajie合并到wukong分支。(需要解决代码冲突)

[rocrocket@wupengchong git21]$ git merge bajie
Auto-merged roc.c
CONFLICT (content): Merge conflict in roc.c
Automatic merge failed; fix conflicts and then commit the result.
[rocrocket@wupengchong git21]$

可以看到提示,是在抱怨bajie的代码和当前分支(wukong)的代码有冲突(conflict)。只好来解决一下了。
可以看到在merge报冲突之后roc.c的内容是这样:
[rocrocket@wupengchong git21]$ cat roc.c

#include
int main()
{
	printf(“Please guess who he is.\n”);
	printf(“He is a youngster.\n”);
	<<<<<<< HEAD:roc.c
	printf(“He is a female.\n”);
	=======
	printf(“He is a male.\n”);
	>>>>>>> bajie:roc.c
	return 0;
}

显然是在等待人脑去判断代码是选择male还是female。呵呵 那我们就来选择正确的,删除错误的,当然是选择male了!修改后的代码如下:
[rocrocket@wupengchong git21]$ cat roc.c

#include
int main()
{
	printf(“Please guess who he is.\n”);
	printf(“He is a youngster.\n”);
	printf(“He is a male.\n”);
	return 0;
}

好,我们解决了冲突,这回可以放心提交了!(注意这回就不用merge了哦,直接commit就可以了!)

[rocrocket@wupengchong git21]$ git diff
diff –cc roc.c
index b8066c1,05aaf78..0000000
— a/roc.c
+++ b/roc.c
[rocrocket@wupengchong git21]$ git add roc.c
[rocrocket@wupengchong git21]$ git diff
[rocrocket@wupengchong git21]$ git commit
Created commit e1ca782: wukong:003
[rocrocket@wupengchong git21]$

好了,commit成功!
查看一下日志,即git log一下:

[rocrocket@wupengchong git21]$ git log
commit e1ca782d95176e83d1013361ba88d0b4c2f51f50
Merge: 4f6ba4e… 5a0f9ee…
Author: rocrocket 
Date:   Thu Oct 9 23:43:06 2008 +0800

wukong:003
Merge branch ‘bajie’ into wukong

Conflicts:

roc.c

commit 4f6ba4e96db6110405f615a5ea5d3119faf64d45
Author: rocrocket 
Date:   Thu Oct 9 23:35:31 2008 +0800

wukong:002

commit 5a0f9ee71fe9ffe259b9b6cf842dd69baa95b307
Author: rocrocket 
Date:   Thu Oct 9 19:16:31 2008 +0800

bajie:001

commit 1ac0798784828577ecb808b03165facfb5bef3e3
Author: rocrocket 
Date:   Thu Oct 9 19:07:57 2008 +0800

wukong:001

commit d5d44dcdc938a263bdbc1c9cb06de24c27adebc7
Author: rocrocket 
Date:   Thu Oct 9 18:50:39 2008 +0800

master:001

看到了么,log好多阿!这回你可以推出一个结论,那就是:git merge的不仅仅是代码,而且还包括log!呵呵

第六步、由我操作,将wukong合并到master分支。

该到衣锦还乡的时候了!

悟空和八戒折腾了半天,是该回头找master(师傅)了。

先切换到master:

[rocrocket@wupengchong git21]$ git checkout master
Switched to branch “master”
[rocrocket@wupengchong git21]$ git branch
bajie
* master
wukong
[rocrocket@wupengchong git21]$

然后就是merge了!

[rocrocket@wupengchong git21]$ git merge wukong
Updating d5d44dc..e1ca782
Fast forward
roc.c |    2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
[rocrocket@wupengchong git21]$

很好,成功了!看看你的master分支的roc.c代码,是不是已经把悟空和八戒的劳动成果吸纳过来了 呵呵~

[rocrocket@wupengchong git21]$ cat -n roc.c
1    #include
2    int main()
3    {
4        printf(“Please guess who he is.\n”);
5        printf(“He is a youngster.\n”);
6        printf(“He is a male.\n”);
7        return 0;
8    }
[rocrocket@wupengchong git21]$

很明显,youngster和male都已经到来了。

第七步、实验完毕。可以删除两个分支了。
好了,实验完毕了,master(师傅)已经将悟空和八戒的劳动成果吸纳为己有了(呵呵),看来悟空和八戒已经可以自由的去玩了。
我们先来删除bajie:

[rocrocket@wupengchong git21]$ git branch -d bajie
Deleted branch bajie.

很好,成功删除。我们使用了前面提到的-d选项,这个选项用于在分支成功合并后删除分支。
还有一个选项叫-D,是大写的d,它主要用于在分支失败后删除分支。
我们用-D删除wukong:

[rocrocket@wupengchong git21]$ git branch -D wukong
Deleted branch wukong.

看,也成功了!这是因为这两个分支都已经成功merge了。
那么在什么时候-D和-d不同呢。下次重点讲解。:)
今天写了很多内容,超级累了,读者一步一步跟下来,一定会对git的工作原理有更深的理解。欣慰欣慰~~ You can count on me!^_^

在之前的讲解中,涉及过git branch删除分支时-D和-d的区别,但没有深入研究,感觉不解渴。
今天,就来分各种情况,好好研究研究-D和-d吧。(今天实验室组织去生存岛玩,很赞但很累。现在已经过了12点,凌晨坚持写完这篇博客… 困极了…)

前提要说清,我们有master主分支、wukong分支共两个分支。

情况一:wukong分支处于已commit状态。master也处于commit状态。然后在master主分支进行-D和-d,看看结果。
结论:
如果master分支执行了merge wukong操作,那么使用-d和-D是没有区别的。
如果master分支没有执行过merge wukong操作,且wukong做出了修改,也就是说master根本没有把wukong的工作吸纳进来,则使用-d会显示:

[rocrocket@wupengchong git22]$ git branch -d wukong
error: The branch ‘wukong’ is not an ancestor of your current HEAD.
If you are sure you want to delete it, run ‘git branch -D wukong’.

如果是用-D当然会成功删除这个分支:

[rocrocket@wupengchong git22]$ git branch -D wukong
Deleted branch wukong.

情况二:wukong分支处于未commit状态。然后在master主分支进行-D和-d,看看结果。
结论:
这种情况更为糟糕,因为当分支未commit时,你甚至是无法转移到master主分支的,瞧:

[rocrocket@wupengchong git23]$ git checkout master
error: Entry ‘roc.c’ not uptodate. Cannot merge.

无法切换到主分支,那么删除wukong分支也就无从谈起了。看来还是要老老实实的commit wukong分支阿。

情况三:wukong分支处于已commit状态。master处于未commit状态。然后在master主分支进行-D和-d,看看结果。
结论:
如果wukong进行了改进,那么在master分支使用-d来删除wukong分支时会失败:

[rocrocket@wupengchong git23]$ git branch -d wukong
error: The branch ‘wukong’ is not an ancestor of your current HEAD.
If you are sure you want to delete it, run ‘git branch -D wukong’.

如果wukong进行了改进,那么使用-D时会成功:

[rocrocket@wupengchong git23]$ git branch -D wukong
Deleted branch wukong.

看来-d其实对于master是没有要求的,只是对wukong分支有要求,即必须是commit的。

情况四:wukong分支处于未commit状态。master也处于未commit状态。然后在master主分支进行-D和-d,看看结果。
结论:
这个情况当然和情况二一样喽。
四种情况,涵盖了大部分-D和-d的情况。足够用了!
ps:这几天找工作,忙阿,投了google, 不过都杳无音信了,还有baidu,tencent,redhat, 继续努力吧 呵呵~
晚安了!

本次讲解“clone和pull的艺术”。大家阅读完本文,就可以掌握如何和其他的合作开发者一起协同开发了!
1 以rocrocket用户来建立一个git仓库,在master主分支里建立roc.c文件,然后建立wukong分支,在其中改进roc.c文件。保证wukong分支为commit状态。然后再改进master分支的代码以使master分支的roc.c为未提交状态。
2 登录到bob用户,利用clone来获取rocrocket的信息
3 以bob为登录用户来改进rocrocket的master的代码,然后切换到rocrocket来pull bob修改的代码。

开始:
1、
以rocrocket用户来建立一个git仓库,在master主分支里建立roc.c文件,然后建立wukong分支,在其中改进roc.c文件。保证wukong分支为commit状态。然后再改进master分支的代码以使master分支的roc.c为未提交状态。

[rocrocket@wupengchong git23]$ ls
roc.c
[rocrocket@wupengchong git23]$ git init
Initialized empty Git repository in .git/
[rocrocket@wupengchong git23]$ git commit -a
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use “git add …” to include in what will be committed)
#
#       roc.c
nothing added to commit but untracked files present (use “git add” to track)
[rocrocket@wupengchong git23]$ git add .
[rocrocket@wupengchong git23]$ git commit
Created initial commit d3d0679: rocrocket:master:001
1 files changed, 6 insertions(+), 0 deletions(-)
create mode 100644 roc.c
[rocrocket@wupengchong git23]$

从这个初始化过程可以看出,git commit -a命令只可以用在已经建立了index file之后,也就是在第一次初始化时,必须要使用git add命令来建立index file,否则会报错。
然后建立wukong分支:

[rocrocket@wupengchong git23]$ git branch wukong
[rocrocket@wupengchong git23]$ git checkout wukong
Switched to branch “wukong”
[rocrocket@wupengchong git23]$ vi roc.c
[rocrocket@wupengchong git23]$ git commit -a
Created commit aad38ac: rocrocket:wukong:001
1 files changed, 1 insertions(+), 0 deletions(-)
[rocrocket@wupengchong git23]$ cat -n roc.c
1  #include
2  int main()
3  {
4          printf(“Please guess who he is.\n”);
5          printf(“He is born in beijing.\n”);
6          return 0;
7  }
[rocrocket@wupengchong git23]$

黑体是在master的基础上新加入的一行代码。好了,wukong分支已经处于commit状态了。现在回到master,再改进下代码,并使得master处于未commit状态:

[rocrocket@wupengchong git23]$ git checkout master
Switched to branch “master”
[rocrocket@wupengchong git23]$ vi roc.c
[rocrocket@wupengchong git23]$ cat -n roc.c
1    #include
2    int main()
3    {
4        printf(“Please guess who he is.\n”);
5        printf(“He is crazy about linux.\n”);
6        return 0;
7    }
[rocrocket@wupengchong git23]$ pwd
/rocrocket/PSB/home/git23

在master分支中加入了一句He is crazy about linux. 并且我故意没有git commit.

2、
登录到bob用户,利用clone来获取rocrocket的信息。

[bob@wupengchong ~]$ whoami
bob
[bob@wupengchong ~]$ git clone /rocrocket/PSB/home/git23 bob23
Initialized empty Git repository in /rocrocket/PSB/bob/bob23/.git/
[bob@wupengchong ~]$ ls
bob23
[bob@wupengchong ~]$ cd bob23/
[bob@wupengchong bob23]$ cat -n roc.c
1  #include
2  int main()
3  {
4          printf(“Please guess who he is.\n”);
5          return 0;
6  }
[bob@wupengchong bob23]$ git branch
* master

我们成功的在bob用户下将rocrocket的代码clone过来了,并放到了bob自己定义的bob23目录下。首先可以看到,我们clone到的是rocrocket的master分支的已commit的代码;而且可以看到当前的分支只有一个master主分支。而在rocrocket里的wukong分支并没有被clone过来。恩,不要沮丧和奇怪。git是这样设计的:clone的话,是clone远端的当前分支。通俗的说,远端当前处在哪个分支,你clone来的就是哪个分支。这下,你该知道如何clone到rocrocket的wukong分支了吧,对!就是让rocrocket也chekcout到wukong分支,然后你再clone就OK了!这个时候你到bob23目录下再git branch会得到只有wukong一个分支。对滴,你要明确一点,不是任何git仓库都有master分支的哦~
By the way, 在执行git checkout branchname时,是必须保证当前本分支处于commit状态,否则git会提示:

[rocrocket@wupengchong git23]$ git checkout wukong
error: Entry ‘roc.c’ not uptodate. Cannot merge.

我们这个时候切换到rocrocket,将未commit的代码commit:

[rocrocket@wupengchong git23]$ git branch
* master
wukong
[rocrocket@wupengchong git23]$ git commit -a
Created commit fadfdb4: rocrocket:master:002
1 files changed, 1 insertions(+), 0 deletions(-)

3、
以bob为登录用户来改进rocrocket的master的代码,然后切换到rocrocket来pull bob修改的代码。

[bob@wupengchong bob23]$ ls
roc.c
[bob@wupengchong bob23]$ git branch
* master
[bob@wupengchong bob23]$ cat roc.c
#include
int main()
{
	printf(“Please guess who he is.\n”);
	return 0;
}
[bob@wupengchong bob23]$ vi roc.c
[bob@wupengchong bob23]$ cat -n roc.c
1    #include
2    int main()
3    {
4        printf(“Please guess who he is.\n”);
5        printf(“His name is roc.\n”);
6        return 0;
7    }
[bob@wupengchong bob23]$ git commit -a
Created commit 7c1cd89: bob:master:001
1 files changed, 1 insertions(+), 0 deletions(-)

改进完毕并成功提交了。下面的任务就是转回到rocrocket来试着pull了!(pull就是取回代码的命令)

[rocrocket@wupengchong git23]$ cat roc.c
#include
int main()
{
	printf(“Please guess who he is.\n”);
	printf(“He is crazy about linux.\n”);
	return 0;
}
[rocrocket@wupengchong git23]$ git pull /rocrocket/PSB/bob/bob23
Unpacking objects: 100% (3/3), done.
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2)remote: , done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Auto-merged roc.c
CONFLICT (content): Merge conflict in roc.c
Automatic merge failed; fix conflicts and then commit the result.
[rocrocket@wupengchong git23]$

可以看出rocrocket的代码和bob的代码是有冲突的,所以在我git pull远端仓库时,提示我conflict。

这时,需要我自己解决冲突了。

[rocrocket@wupengchong git23]$ vi roc.c
[rocrocket@wupengchong git23]$ cat -n roc.c
1    #include
2    int main()
3    {
4        printf(“Please guess who he is.\n”);
5        printf(“He is crazy about linux.\n”);
6        printf(“His name is roc.\n”);
7        return 0;
8    }
[rocrocket@wupengchong git23]$

好,解决完冲突了,下面来commit吧!

[rocrocket@wupengchong git23]$ git commit -a
Created commit ed92cd2: new pull!

好了 一切都安静了,我们已经成功把bob的工作合并到rocrocket的工作之中了。
下次将重点讲解如何更好的获取别人的代码。

今天重点讨论如何更好的从别人那里获取工作进展。你是否想过,在一个人员繁多的项目组里,你是很难深入了解每个人的编程能力的,那么你的员工编写完了代码,你是否会一下就pull到你的master主分支呢?如果你会,那么你胆子够大。不过大部分project manager不会这样做的,他们会先把代码取过来放到一个临时的地方,仔细考究员工编写的代码,认为代码合格后,才会放心的放到master中去。
我们即将学习和研究的就是这种git安全处理机制的操作方法。

首先在rocrocket的git24目录下建立一个新roc.c文件。然后在bob用户下git clone这个git24目录到本地的bob24目录下。

[bob@wupengchong ~]$ git clone /rocrocket/PSB/home/git24 bob24
Initialized empty Git repository in /rocrocket/PSB/bob/bob24/.git/
[bob@wupengchong ~]$ ls
bob24
[bob@wupengchong ~]$

好了,已经clone过来了!下面bob来改进代码:

[bob@wupengchong ~]$ cd bob24/
[bob@wupengchong bob24]$ ls
roc.c
[bob@wupengchong bob24]$ git branch
* master
[bob@wupengchong bob24]$ vi roc.c
[bob@wupengchong bob24]$ cat -n roc.c
1    #include
2    int main()
3    {
4        printf(“Hello,everyone!\n”);
5        printf(“Good bye!\n”);
6    }
[bob@wupengchong bob24]$ git commit -a
Created commit 42b7069: bob:001
1 files changed, 1 insertions(+), 0 deletions(-)
[bob@wupengchong bob24]$

bob已经改进了代码,并成功提交了!
下面就要到重头戏了,如何安全的处理员工的代码。
我们切换到rocrocket(也就是PM的角色),

[rocrocket@wupengchong git24]$ git fetch /rocrocket/PSB/bob/bob24 master:bob
Unpacking objects: 100% (3/3), done.
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2)remote: , done.
remote: Total 3 (delta 0), reused 0 (delta 0)
From /rocrocket/PSB/bob/bob24
* [new branch]      master     -> bob
[rocrocket@wupengchong git24]$

看到了吧,PM用了一招,叫fetch!用这个命令,可以将远端的代码先下载到本地的指定分支(例子中是bob)中,而不是像git pull那样直接合并到当前分支。

[rocrocket@wupengchong git24]$ git branch
bob
* master
[rocrocket@wupengchong git24]$ git checkout bob
Switched to branch “bob”
[rocrocket@wupengchong git24]$ ls
roc.c
[rocrocket@wupengchong git24]$ cat roc.c
#include
int main()
{
	printf(“Hello,everyone!\n”);
	printf(“Good bye!\n”);
}
[rocrocket@wupengchong git24]$

我们切换到bob分支并开始检查代码。告诉你一个新命令,这个命令可以很方便的查看两个分支的代码区别,它就是git-whatchanged!

[rocrocket@wupengchong git24]$ git-whatchanged -p master..bob
commit 42b706951ed78bad6ce716f61ca56fe63ad61067
Author: bob 
Date:   Fri Oct 10 15:03:30 2008 +0800

bob:001

diff –git a/roc.c b/roc.c
index 33e540b..885aa26 100644
— a/roc.c
+++ b/roc.c
@@ -2,4 +2,5 @@
int main()
{
printf(“Hello,everyone!\n”);
+       printf(“Good bye!\n”);
}
[rocrocket@wupengchong git24]$

看,已经显示的很清楚了!经过PM的审核,可以放心地把代码加入到master中了!

[rocrocket@wupengchong git24]$ git checkout master
Switched to branch “master”
[rocrocket@wupengchong git24]$ git pull . bob
Updating dfa52c4..42b7069
Fast forward
roc.c |    1 +
1 files changed, 1 insertions(+), 0 deletions(-)
[rocrocket@wupengchong git24]$

我们仍然坚持使用了git pull命令来进行合并工作。其实此时,我们使用merge也未尝不可。
git-pull的第一个参数表示仓库的位置,我们使用“.”表示在当前目录下。
git-pull的第二个参数表示分支名称,我们使用bob表示了分支名称。
git-pull默认就是将远端代码获取来并合并到当前分支中。
好了,任务完成!
最后总结一下:PM使用git fetch将远端代码获取到一个本地新分支中,然后使用git-whatchanged来查看区别,最后用git pull来将代码合并到本地分支中。

这次重点讲解git reset这条命令。这个命令主要是用在恢复代码上。当你发现自己编写的一段已提交的代码是错误的,那么你就会用到git reset了!
1 讲解git reset –soft
2 讲解git reset –hard
3 讲解git reset –mixed
4 讲解git reset
5 讲解git reset –

开始:
1、
讲解git reset –soft

[rocrocket@wupengchong ~]$ mkdir git25
[rocrocket@wupengchong ~]$ cd git25/
[rocrocket@wupengchong git25]$ vi roc.c
[rocrocket@wupengchong git25]$ cat -n  roc.c
1  #include
2  int main()
3  {
4          printf(“He is a young man.\n”);
5          return 0;
6  }
[rocrocket@wupengchong git25]$ git init
Initialized empty Git repository in .git/
[rocrocket@wupengchong git25]$ git add .
[rocrocket@wupengchong git25]$ git commit -m “1″
Created initial commit a2909db: 1
1 files changed, 6 insertions(+), 0 deletions(-)
create mode 100644 roc.c
[rocrocket@wupengchong git25]$ git log
commit a2909db30720fe7bb85724fdfad89b5ffd280b05
Author: rocrocket 
Date:   Wed Oct 15 08:46:20 2008 +0800

1
[rocrocket@wupengchong git25]$ vi roc.c
[rocrocket@wupengchong git25]$ cat -n roc.c
1  #include
2  int main()
3  {
4          printf(“He is a young man.\n”);
5          printf(“He is 24 years old.\n”);
6          return 0;
7  }
[rocrocket@wupengchong git25]$ git commit -a -m “2″
Created commit 5fcb5ab: 2
1 files changed, 1 insertions(+), 0 deletions(-)
[rocrocket@wupengchong git25]$ git log
commit 5fcb5abeee4dea39da8946bd39c6daea19977558
Author: rocrocket 
Date:   Wed Oct 15 08:47:10 2008 +0800

commit a2909db30720fe7bb85724fdfad89b5ffd280b05
Author: rocrocket 
Date:   Wed Oct 15 08:46:20 2008 +0800

1
[rocrocket@wupengchong git25]$

到目前为止,我们建立了一个git仓库,并进行了两次commit。
此时,我发现我的commit是错误的,此人不是24岁,而是25,我想撤销第二次commit。看看用–soft会是什么效果:

[rocrocket@wupengchong git25]$ git reset –soft HEAD^
[rocrocket@wupengchong git25]$ git log
commit a2909db30720fe7bb85724fdfad89b5ffd280b05
Author: rocrocket 
Date:   Wed Oct 15 08:46:20 2008 +0800

1
[rocrocket@wupengchong git25]$ git diff
[rocrocket@wupengchong git25]$ git diff –cached
diff –git a/roc.c b/roc.c
index b089322..ee25766 100644
— a/roc.c
+++ b/roc.c
@@ -2,5 +2,6 @@
int main()
{
printf(“He is a young man.\n”);
+       printf(“He is 24 years old.\n”);
return 0;
}
[rocrocket@wupengchong git25]$ git diff HEAD
diff –git a/roc.c b/roc.c
index b089322..ee25766 100644
— a/roc.c
+++ b/roc.c
@@ -2,5 +2,6 @@
int main()
{
printf(“He is a young man.\n”);
+       printf(“He is 24 years old.\n”);
return 0;
}
[rocrocket@wupengchong git25]$ cat -n roc.c
1  #include
2  int main()
3  {
4          printf(“He is a young man.\n”);
5          printf(“He is 24 years old.\n”);
6          return 0;
7  }
[rocrocket@wupengchong git25]$

你如果一步一步按照我的设计运行了上面的命令,你会发现git reset之后,git diff返回空,而git diff –cached和git diff HEAD会返回有效信息。这说明使用–soft选项后,只回退了commit的信息,而不会回复到index file一级。哈哈,这就明了了!你如果想撤销commit,并且只回退commit的信息,那么就用–soft吧!
而且你可以观察到git reset的意思是“撤销到哪个位置”,。也就是说代码管理者需要在后面的参数中指定一个之前的commit位置。如上面提到的HEAD^。

2、
讲解git reset –hard
有了–soft的试验思路,我想你也应该知道如何测试–hard了。有意思的工作留给你去自己完成吧。我只说结论:
–hard会完全撤销一个commit,彻底的回复到上一次commit的状态。连working tree的源代码也会完全倒退到上次commit之时的状态。所以使用–hard后,git diff,git diff –cached和git diff HEAD都会返回空。
有了这个–hard好工具,你可以这样做:在当前的current working tree中修改了代码,你可以选择git add或者不add,然后使用git reset –hard HEAD命令就可以恢复到修改之前的最初状态了。你修改的代码和git add的信息都会被丢弃。这个用法记住它,早晚你会用到它。但往往你会武断的认为git reset只能恢复到之前的commit状态,但你往往想不到git reset还可以恢复到当前的HEAD所指定的commit状态。

3、
讲解git reset –mixed
–mixed选项会撤销最近的一次commit,只保留working tree的源代码级的修改,而index file和commit都会回复到上一次commit的状态。所以使用–mixed后,git diff和git diff HEAD会有有效信息的输出,而git diff –cached会输出空。

4、
讲解git reset
我只需要告诉你–mixed是git reset的默认选项。你应该知道了,git reset和git reset –mixed效果是完全一样的。

5、
讲解git reset –
如果你想从index file中删除一个已登记的文件,那么就用这个命令。
比如,我想删除index file里登记的roc.c,那么就$ git reset — roc.c就可以了!
这个功能似乎很奇怪,什么时候会用到呢?哦!是这样,如果你刚刚git add了一个文件到index file里,但是你突然发现这是错误的,那么就要用到git reset –喽!

在之二十五中讲到了git reset的用法,有个朋友问了我N个关于reset的问题。我认为可能是他对working tree、index file和commit的关系和区别还不是很明白(明显是没有仔细看我前面的日记 呵呵)
下面总结一下git reset的各个选项吧:
1 git reset –soft 只撤销commit,保留working tree和index file。
2 git reset –hard 撤销commit、index file和working tree,即撤销销毁最近一次的commit
3 git reset –mixed 撤销commit和index file,保留working tree
4 git reset和git reset –mixed完全一样
5 git reset –用于删除登记在index file里的某个文件。

linux下,一个运行中的程序,究竟占用了多少内存?

linux下,一个运行中的程序,究竟占用了多少内存?

1. 在linux下,查看一个运行中的程序, 占用了多少内存, 一般的命令有

(1). ps aux:
其中 VSZ(或VSS)列 表示,程序占用了多少虚拟内存。
RSS列 表示, 程序占用了多少物理内存。
虚拟内存可以不用考虑,它并不占用实际物理内存。

(2). top 命令也可以
其中 VIRT(或VSS)列 表示,程序占用了多少虚拟内存。 同 ps aux 中的 VSZ列
RES列 表示, 程序占用了多少物理内存。同 ps aux 中的RSS列

2.在linux下, 查看当前系统占用了多少内存, 一般的命令是 free
其中, free就是系统还有多少内存可以使用。
但由于 linux 系统对内存使用有一个原则, 就是, 内存是宝贵的, 能使用多少就使用多少。 所以, linux会把已经调用过的包缓存起来,放在内存里。
这样,实际上,可以使用的内存,就可以理解为, free+buffers+cached

3.当你了解完这些命令以后, 再去使用ps aux 命令去查看的时候, 会发现一个奇怪的现象。
所有的 RSS 列的数据,加起来, 比物理内存的数要大很多。
比如, 物理内存为2G, 而RSS列的数据加起来,可能有5个G之多, 这是怎么回事了?
这是因为RSS列的值骗了我们。

linux的内存机制是这样的:
在运行一个程序时, linux会调用该程序依赖的链接库, 如lib.xx.so。 首先看该链接库是否被映射进内存中,如果没有被映射,则将代码段与数据段映射到内存中,否则只是将其加入进程的地址空间。
这样,当N个程序,依赖到lib.xx.so的时候, 实际上,内存中只有一个lib.xx.so ,而不是N个。而RSS在显示一个程序占用的实际物理内存时, 将lib.xx.so也算了进来。
比如, X程序, 本身占用内存为5M, lib.xx.so 占用内存2M,lib.xx.so被N个程序共享依赖。 则RSS显示为,X程序运行,占用内存为7M。 实际上, X程序占用了5M空间。 多余的2m被讨入到RSS中了。当你在用ps aux显示内存占用情况时, N个共享依赖lib.xx.so的N个程序,都把这2m空间,算在自己的RSS中了, 这样RSS的sum值,就比实际物理内存多了。当然, linux的内存使用机制很复杂, 不是一句两句能说清楚的。这里只是简单的说明了一下, ps aux中的RSS值, 并不能真实反映物理内存的使用情况。

4. 如果查看更详细的内存使用情况, 可用以下几种方法, 或者几种方法结合使用:
这几种方法,都需要root账户的权限
(1). pmap -d $pid
$pid 是正在运行的程序的pid

(2). cat /proc/$pid/smaps
smaps的数据比较详细,可简单的归纳一下,归纳的命令如下:

	cat /proc/$pid/smaps  | awk '/Size|Rss|Pss|Shared|Private|Referenced|Swap/{val_name=gensub(/([a-zA-Z_]*).*/,"\\1",1,$1); list[val_name]+=$2; }END{for(val in list)print val,list[val];}'                     
	

(3). cat /proc/$pid/maps

(4). cat /proc/$pid/statm
输出解释(单位都是使用的内存页数,一页内存的大小一般为4k)
第一列 size:任务虚拟地址空间大小
第二列 Resident:正在使用的物理内存大小
第三列 Shared:共享页数
第四列 Trs:程序所拥有的可执行虚拟内存大小
第五列 Lrs:被映像倒任务的虚拟内存空间的库的大小
第六列 Drs:程序数据段和用户态的栈的大小
第七列 dt:脏页数量

(5). vmstat
这个命令据说也可以提供一些参考信息,具体还未研究

5.作为phper,尝试过使用php的函数memory_get_usage(), 该函数也不能得到php当前运行的程序,实际的,真正占用的内存数量。
如果真想得到,php真正占用的内存, 大概只能在, 程序运行的开始,执行一次memory_get_usage().
在程序运行结束,执行一次memory_get_usage()。 将两者的值相减,得到的值, 应该是一个相对比较准确的,内存占用数量了。
这个方法还没有测试, 考虑到, 得到这个数量,也没有实际意义, 加上平时又比较忙,懒得试了。
也许php还有一个方法, 是使用shm_* 系列函数, 这也我也未深入研究,详见这篇文章(http://duckweeds.blog.sohu.com/166663796.html)

6.另外还有一些文章可以参考,如下:
(1)一个C程序员, 眼中的Linux内存使用详解,写的比较详细,比较细致,也比较专业。
(2)对 /proc/pid/statm的详细说明
(3)简单解读linux的/proc下的statm、maps、memmap 内存信息文件分析
(4)php 共享内存的使用
(5)Memory Usage with smaps
(6)Capturing Process Memory Usage Under Linux,这篇文章似乎是对一个产品的广告,但里面对USS,PSS,RSS 这几个概念有详细的解释
(7) ELC: How much memory are applications really using,跟(6)一样,是对同一个产品的广告,文章里有一些东西可以参考
(8) Linux Check Memory Usage,文章对 free, vmstat,top , gnome-system-monitor等命令有一些介绍
(9)Console Monitoring Tools for SUSE Linux,对top,free,uptime,pmap,smartctl,iostat,strace等命令有所介绍,并且介绍的比较详细,目前只是粗略的看了一下,有时间还要再看看。
(10)Solaris 9 Enhanced pmap,比较详细的介绍了pmap的应用,不过是基于Solaris 9的

命令格式:pmap

	[root@localhost security]# pmap -d 4993
	4993: -bash
	Address Kbytes Mode Offset Device Mapping
	08047000 596 r-x-- 0000000000000000 003:00006 bash bash的代码段
	080dc000 28 rwx-- 0000000000094000 003:00006 bash bash的数据段
	080e3000 280 rwx-- 00000000080e3000 000:00000 [ anon ] bash的堆
	4d575000 84 r-x-- 0000000000000000 003:00006 ld-2.3.4.so 共享库的代码段
	4d58a000 4 r-x-- 0000000000015000 003:00006 ld-2.3.4.so 共享库的数据段
	4d58b000 4 rwx-- 0000000000016000 003:00006 ld-2.3.4.so 共享库的堆
	4d58e000 1164 r-x-- 0000000000000000 003:00006 libc-2.3.4.so
	4d6b1000 4 r-x-- 0000000000123000 003:00006 libc-2.3.4.so
	4d6b2000 12 rwx-- 0000000000124000 003:00006 libc-2.3.4.so
	4d6b5000 8 rwx-- 000000004d6b5000 000:00000 [ anon ] 匿名物理内存,
	4d6de000 8 r-x-- 0000000000000000 003:00006 libdl-2.3.4.so
	4d6e0000 8 rwx-- 0000000000001000 003:00006 libdl-2.3.4.so
	4d807000 12 r-x-- 0000000000000000 003:00006 libtermcap.so.2.0.8
	4d80a000 4 rwx-- 0000000000002000 003:00006 libtermcap.so.2.0.8
	b7bc2000 176 r-x-- 0000000000000000 003:00006 GB18030.so
	b7bee000 8 rwx-- 000000000002b000 003:00006 GB18030.so
	b7bf0000 24 r-xs- 0000000000000000 003:00006 gconv-modules.cache
	b7bf6000 1060 r-x-- 0000000002197000 003:00006 locale-archive
	b7cff000 2048 r-x-- 0000000000000000 003:00006 locale-archive
	b7eff000 36 r-x-- 0000000000000000 003:00006 libnss_files-2.3.4.so
	b7f08000 8 rwx-- 0000000000008000 003:00006 libnss_files-2.3.4.so
	b7f0a000 8 rwx-- 00000000b7f0a000 000:00000 [ anon ]
	b7f20000 8 rwx-- 00000000b7f20000 000:00000 [ anon ]
	bff0d000 84 rw--- 00000000bff0d000 000:00000 [ stack ]
	ffffe000 4 ----- 0000000000000000 000:00000 [ anon ]
	mapped: 5680K writeable/private: 460K shared: 24K
	

每列的含义如下:
参数 解释
Address:00378000-0038d000 进程所占的地址空间
Kbytes 该虚拟段的大小
RSS 设备号(主设备:次设备)
Anon 设备的节点号,0表示没有节点与内存相对应
Locked 是否允许swapped
Mode 权限:r=read, w=write, x=execute, s=shared, p=private(copy on write)
Mapping: bash 对应的映像文件名