0%

极具归属感的 - chown

.. note::
常记溪亭日暮,沉醉不知归路。
李清照《如梦令》

Linux chown 命令用于设置文件所有者和文件关联组的命令。

官方的定义为:

chown - change file owner and group

Linux/Unix 的有个理念就是一切皆文件,而对于每个文件也是如chmod所述,均拥有所有者。

此时就可以利用 chown 指定文件的拥有者或者指定的用户或组,用户可以是用户名或者用户 ID,组可以是组名或者组 ID,文件是以空格分开的要改变权限的文件列表,支持通配符

不过需要注意的是 chown 需要超级用户 root 的权限才能执行此命令,或者使用sudo也可以。

使用语法

使用语法如下:

1
2
3
$ chown [option] [user[:group]] file...
# 或者
$ chown [option] --reference=RFILE file...

其中user为新的文件拥有者的用户名或者IDgroup为新的文件拥有者的用户组名或ID****。

并且可以通过--referenc=RFILE选项来设定希望修改的文件和目录。

其他的选项可以为: :

  • -c : 与-v类似,不过只显示更改的信息
  • -R : 递归地处理指定的目录以及其子目录下的所有文件

通用实例

最简单的使用方式应该就是指定用户和用户组了,如下:

1
2
3
4
5
6
7
$ ll 
-rw-rw-r--. 1 user user 5 May 7 14:56 a

$ sudo chown user1:group1 a

$ ll
-rw-rw-r--. 1 user1 group1 5 May 7 14:56 a

上面的命令将把a指定为用户user1,组group1。注意user1group1必须存在,不然会提示无效的用户或者组。

更新组而不更新用户

这个选项一般用在,希望把某个用户的文件共享到一个组,此时的方法如下:

1
$ sudo chown :newgroup filename

此时的用户所有者不变,而仅仅更改了文件所属组。

提示更新

1
2
3
4
5
6
7
8
9
10
$ sudo chown -c user1 a b c d
changed ownership of "b" from user to user1
changed ownership of "c" from user to user1
changed ownership of "d" from user to user1

$ sudo chown -v user1 a b c d
changed ownership of "a" from user to user1
changed ownership of "b" from user to user1
changed ownership of "c" from user to user1
changed ownership of "d" from user to user1

从这个例子可以看出,对于-c和-v的区别,-v全部显示,而-c仅仅显示更新的部分。

递归处理文件或文件夹

1
$ sudo chown -R user:group file directory

此条命令将递归地将文件file和目录directory及其子目录的文件更新为user用户拥有,group组拥有。

Linux chown 命令

Linux chown 命令用于设置文件所有者和文件关联组的命令。

官方的定义为:

chown - change file owner and group

Linux/Unix 的有个理念就是一切皆文件,而对于每个文件也是如chmod所述,均拥有所有者。

此时就可以利用 chown 指定文件的拥有者或者指定的用户或组,用户可以是用户名或者用户 ID,组可以是组名或者组 ID,文件是以空格分开的要改变权限的文件列表,支持通配符

不过需要注意的是 chown 需要超级用户 root 的权限才能执行此命令,或者使用sudo也可以。

使用语法

使用语法如下:

1
2
3
$ chown [option] [user[:group]] file...
# 或者
$ chown [option] --reference=RFILE file...

其中user为新的文件拥有者的用户名或者IDgroup为新的文件拥有者的用户组名或ID****。

并且可以通过--referenc=RFILE选项来设定希望修改的文件和目录。

其他的选项可以为: :

  • -c : 与-v类似,不过只显示更改的信息
  • -R : 递归地处理指定的目录以及其子目录下的所有文件

通用实例

最简单的使用方式应该就是指定用户和用户组了,如下:

1
2
3
4
5
6
7
$ ll 
-rw-rw-r--. 1 user user 5 May 7 14:56 a

$ sudo chown user1:group1 a

$ ll
-rw-rw-r--. 1 user1 group1 5 May 7 14:56 a

上面的命令将把a指定为用户user1,组group1。注意user1group1必须存在,不然会提示无效的用户或者组。

更新组而不更新用户

这个选项一般用在,希望把某个用户的文件共享到一个组,此时的方法如下:

1
$ sudo chown :newgroup filename

此时的用户所有者不变,而仅仅更改了文件所属组。

提示更新

1
2
3
4
5
6
7
8
9
10
$ sudo chown -c user1 a b c d
changed ownership of "b" from user to user1
changed ownership of "c" from user to user1
changed ownership of "d" from user to user1

$ sudo chown -v user1 a b c d
changed ownership of "a" from user to user1
changed ownership of "b" from user to user1
changed ownership of "c" from user to user1
changed ownership of "d" from user to user1

从这个例子可以看出,对于-c和-v的区别,-v全部显示,而-c仅仅显示更新的部分。

递归处理文件或文件夹

1
$ sudo chown -R user:group file directory

此条命令将递归地将文件file和目录directory及其子目录的文件更新为user用户拥有,group组拥有。

炫技 - 通过其他文件指定

如果有1个文件或文件夹的权限是你认为可以参考的,此时可以通过下面的命令直接指定,而不需要指定user:group这个参数了。如下两个命令效果一致:

1
2
3
4
5
6
7
8
9
$ ll 
-rw-rw-r--. 1 user1 group1 5 May 7 14:56 a
-rw-rw-r--. 1 user2 group2 5 May 7 14:56 b

# 将b的权限更改为与a一致
# Method1
$ sudo chown user1:group1 b
# Method 2
$ sudo chown --reference=a b

低调但大胆的 - dd 命令

除了入门介绍的基本功能,今天再加点干货。

测试写入速度

1
dd if=/dev/zero of=testfile bs=1G count=1 oflag=direct

这个命令增加了一个选项叫做oflag=direct

  • oflag=direct:确保数据直接写入磁盘,跳过系统缓存。

这个命令将会输出写入速度。完成测试后,可以删除 testfile 文件。

测试读取速度

首先写一个测试文件(可以使用上面的写入命令),然后执行以下读取命令:

1
dd if=testfile of=/dev/null bs=1G count=1 iflag=direct

同样的增加了iflag=direct,可以加速:

  • iflag=direct:直接读取数据,跳过系统缓存。

这样可以得到硬盘的读取速度。

#######TODO

backup an entire copy of a hard disk to another hard disk

1
# dd if=/dev/sda of=/dev/sdb conv=sync,noerror
   cbs=BYTES
          convert BYTES bytes at a time

   conv=CONVS
          convert the file as per the comma separated symbol list


   ibs=BYTES
          read up to BYTES bytes at a time (default: 512)


   iflag=FLAGS
          read as per the comma separated symbol list

   obs=BYTES
          write BYTES bytes at a time (default: 512)

   oflag=FLAGS
          write as per the comma separated symbol list

   seek=N skip N obs-sized blocks at start of output

   skip=N skip N ibs-sized blocks at start of input

   status=LEVEL
          The  LEVEL of information to print to stderr; 'none' suppresses everything but error messages, 'noxfer'
          suppresses the final transfer statistics, 'progress' shows periodic transfer statistics

   N and BYTES may be followed by the following multiplicative suffixes: c =1, w =2, b =512, kB =1000,  K  =1024,
   MB =1000*1000, M =1024*1024, xM =M, GB =1000*1000*1000, G =1024*1024*1024, and so on for T, P, E, Z, Y.

   Each CONV symbol may be:

   ascii  from EBCDIC to ASCII

   ebcdic from ASCII to EBCDIC

   ibm    from ASCII to alternate EBCDIC

   block  pad newline-terminated records with spaces to cbs-size

   unblock
          replace trailing spaces in cbs-size records with newline

   lcase  change upper case to lower case

   ucase  change lower case to upper case

   sparse try to seek rather than write the output for NUL input blocks

   swab   swap every pair of input bytes

   sync   pad  every  input  block with NULs to ibs-size; when used with block or unblock, pad with spaces rather
          than NULs

   excl   fail if the output file already exists

   nocreat
          do not create the output file

   notrunc
          do not truncate the output file

   noerror
          continue after read errors

   fdatasync
          physically write output file data before finishing

   fsync  likewise, but also write metadata

   Each FLAG symbol may be:

   append append mode (makes sense only for output; conv=notrunc suggested)

   direct use direct I/O for data

   directory
          fail unless a directory

   dsync  use synchronized I/O for data

   sync   likewise, but also for metadata

   fullblock
          accumulate full blocks of input (iflag only)

   nonblock
          use non-blocking I/O

   noatime
          do not update access time

   nocache
          Request to drop cache.  See also oflag=sync

   noctty do not assign controlling terminal from file

   nofollow
          do not follow symlinks

   count_bytes
          treat 'count=N' as a byte count (iflag only)

   skip_bytes
          treat 'skip=N' as a byte count (iflag only)

   seek_bytes
          treat 'seek=N' as a byte count (oflag only)

   Sending a USR1 signal to a running 'dd' process makes it print I/O statistics to standard error and  then  re‐
   sume copying.

使用dd命令克隆整个系统

进入Linux操作系统,打开命令行,执行如下命令:

1
sudo  fdisk -u -l

可以查看所有磁盘上的所有分区的尺寸和布局情况。

-u,让start和end中数字的单位是512字节,也就是一个sector扇区的大小。

具体步骤

找一个U盘,安装UbuntuLive Cd系统。

U盘启动,进入盘上的Ubuntu系统,打开命令行,执行:

1
sudo  fdisk -u -l /dev/sda

查看硬件的分区情况。

然后执行:

1
dd   bs=512 count=[fdisk命令中最大的end数+1] if=/dev/sda of=/ghost.img

这样,就可以把我需要的分区数据全部copy到ghost.img文件中。镜像制作完成了!

然后,我们就可以把U盘插到其他系统上,用U盘启动,进入UbuntuLiveCD,打开命令行,执行如下命令:

1
dd if=/ghost.img of=/dev/sda

完成后,拔掉U盘,启动计算机,就可以看到我们的Linux系统已经安装完毕了!

注意:不要直接在计算机上用本地磁盘启动系统后执行dd命令生成本地磁盘的镜像。而应该使用livecd启动计算机。因此计算机运行时会对系统盘产生大量写操作。 直接对运行中的系统盘生成的镜像,在恢复到其他硬盘上时,很可能会无法启动!

一样适用于非Linux操作系统, 在linux上用dd命令实现系统镜像备份和恢复,是不是很简单呢?

对于Windows系统,甚至Mac等等任意系统,其实都可以用dd命令实现系统镜像的备份和恢复。
因为,Linux的fdisk命令能够识别任意系统下的分区格式。fdisk并不关系分区上的文件系统,甚至有无文件系统都不关心。fdisk总是可以报告分区占用了哪些扇区。
dd命令也不关心磁盘的文件系统格式,它只是简单地按照要求从指定的位置,复制多少字节数据而已。
dd命令实现镜像备份和恢复,比Ghost软件简单和强大多了。使用ghost软件,依然需要用户进行复杂而危险的磁盘分区操作。
而使用fdisk和dd这两条命令,一切都免了!

压缩和解压缩

可能我们需要备份的分区很大,使用dd命令生成的镜像文件也就很大。存储和传输这些镜像不太方便。 我们也可以使用压缩程序压缩生成的镜像文件。 这里,我选择使用gzip程序,配合dd命令一起使用。

gzip参数:

  • -c 表示输出到stdout
  • -d 表示解压缩
  • -1 表示最快压缩
  • -9 表示最好压缩

默认使用的是-6压缩级别。

要使用 dd 和 gzip 生成压缩的镜像文件,可以执行命令:

1
# dd bs=512 count=[fdisk命令中最大的end数+1] if=/dev/sda | gzip -6 > /ghost.img.gz

还原时,可以执行下列命令: # gzip -dc /ghost.img.gz.gz | dd of=/dev/sda

提醒:
如果你把镜像恢复到另一台计算机上,你可能会发现你的网卡是eth1,而不是eth0。这是因为
/etc/udev/rules.d/70-persistent-net.rules 文件把你做镜像的计算机的网卡作为eth0登记了。
如果你的网络脚本对eth0进行了处理,而没有对eth1进行处理,那么不修改网络脚本,你可能就无法上网了。

也许你会希望在做镜像之前,先删除 /etc/udev/rules.d/70-persistent-net.rules 文件。这样你恢复镜像时,网卡的名字就是eth0。 就不会造成你在恢复后的计算机上无法上网的问题了。

输入!和你要重新执行的命令的前几个字母。

例如!ps,回车,就会执行最近历史命令中以“ps”开头的比如“ps aux | grep kernel”的命令。

1
# !ps

CentOS 6安装和配置VNC

VNC是Linux上的一款非常优秀的远程控制工具软件,通常我们在Windows上面安装vnc客户端软件来远程访问Linux机器(Windows上常用的客户端RealVNC),要远程连接到Linux首先要确保Linux上面已经安装了VNC server,下面以CentOS 6为例来说明:

1. 检查是否已经安装了VNC server

1
2
3
4
5
6
7
8
9
[root@centos6 ~]# rpm -qa | grep vnc

tigervnc-1.0.900.17.20110314svn4359.el6.i686

gtk-vnc-0.3.103.el6.i686

tigervnc-server-1.0.900.17.20110314svn4359.el6.i686

gtk-vnc-python-0.3.103.el6.i686

如果上面的几个rpm包已经存在,说明VNC server已经安装好了,接下来跳转步骤3启动vncserver就可以了,否则执行步骤2

2. 安装VNC server

1
2
3
# yum install tigervnc

# yum install tigervnc-server

3. 启动vncserver

第一次启动vncserver需要输入密码两次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@centos6 mnt]# vncserver

You will require a password to access your desktops.

Password:

Verify:

New ‘centos6.xman.org:1 (root)’ desktop is centos6.xman.org:1

Creating default startup script /root/.vnc/xstartup

Starting applications specified in /root/.vnc/xstartup

Log file is /root/.vnc/centos6.xman.org:1.log

可以看到已经生成了一个desktop:centos6.xman.org:1

为了方便,可见将vncserver添加到开机启动的服务中,使用setup->System services, 选择vncserver保存即可

接下来在VNC客户端输入上面的desktop就可以连接上Linux的desktop,如果发现连接不上,有可能是下面两个原因导致的:

(1) 本地windows的hosts文件中没有配置远程Linux的hostname,解决方案也很简单,只需要将desktop的hostname替换为IP地址或者在本地windows的hosts文件中添加相应的hostname与IP的映射关系即可

(2) 是由于Linux的防火墙阻止了,这时我们可以选择关闭防火墙或者将VNC的服务端口加入到Linux防火墙的信任列表

关闭防火墙

1
# /sbin/service iptables stop

关于EPEL源

以前在fedora上都是使用163或者sohu的源,感觉速度很快,包也挺全的,但是在CentOS上就不能这么用了,忽然就碰到了EPEL,简直是柳暗花明又一村呀,以前一直为CentOS的稳定而庆幸,为N多包要自己rpm而小失望,现在加上这个源一切都是向fedora的yum体验靠拢呀,赞(≧▽≦)/

企业版 Linux 附加软件包(以下简称 EPEL)是一个由特别兴趣小组创建、维护并管理的,针对 红帽企业版 Linux(RHEL)及其衍生发行版(比如 CentOS、Scientific Linux、Oracle Enterprise Linux)的一个高质量附加软件包项目。

EPEL 的软件包通常不会与企业版 Linux 官方源中的软件包发生冲突,或者互相替换文件。EPEL 项目与 Fedora 基本一致,包含完整的构建系统、升级管理器、镜像管理器等等。

使用方法:

添加源

创建一个文件/etc/yum.repos.d:

1
2

# touch /etc/yum.repos.d/epel.repo

添加以下内容

1
2
3
4
5
[epel]
name=epel repo
baseurl=http://dl.fedoraproject.org/pub/epel/6/$basearch/
gpgcheck=0
enabled=1

当然,如果你的CentOS是5.X版本的就把上面的6改成5即可了。

使用yum更新

1
2
# yum update
# yum install ipython.noarch

现在再试试以前不能安装的诸如ntfs-3g,ipython等等,赞

split - 精准划分

Linux split命令用于将一个文件切分开,一般用于将大文件切分为多个小文件,方便数据传输、保持和校验等。

默认情况下将按照每1000行切割成一个小文件。

官方定义为:

split - split a file into pieces

使用方法为:

1
$ split [OPTION]... [INPUT [PREFIX]]

常用的参数为:

  • -b, --bytes=SIZE : 指定每多少字节切成一个小文件

默认无参数

默认情况下,split 会将原来的大文件aa 切割成多个以x开头的小文件,可以看到其实为xaa,xab,一致到xaz,递增为xba以此类推。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$ split aa
$ ls
-rw-rw-r-- 1 user user 611037792 Jan 15 22:09 aa
-rw-rw-r-- 1 user user 356533 Jan 15 22:10 xaa
-rw-rw-r-- 1 user user 377414 Jan 15 22:10 xab
-rw-rw-r-- 1 user user 346342 Jan 15 22:10 xac
-rw-rw-r-- 1 user user 358728 Jan 15 22:10 xad
-rw-rw-r-- 1 user user 391466 Jan 15 22:10 xae
-rw-rw-r-- 1 user user 368786 Jan 15 22:10 xaf
-rw-rw-r-- 1 user user 377274 Jan 15 22:10 xag
-rw-rw-r-- 1 user user 393500 Jan 15 22:10 xah
-rw-rw-r-- 1 user user 362512 Jan 15 22:10 xai
-rw-rw-r-- 1 user user 365170 Jan 15 22:10 xaj
-rw-rw-r-- 1 user user 362878 Jan 15 22:10 xak
-rw-rw-r-- 1 user user 387394 Jan 15 22:10 xal
-rw-rw-r-- 1 user user 355614 Jan 15 22:10 xam
-rw-rw-r-- 1 user user 366420 Jan 15 22:10 xan
-rw-rw-r-- 1 user user 368912 Jan 15 22:10 xao
-rw-rw-r-- 1 user user 350226 Jan 15 22:10 xap
-rw-rw-r-- 1 user user 386102 Jan 15 22:10 xaq
-rw-rw-r-- 1 user user 377292 Jan 15 22:10 xar
-rw-rw-r-- 1 user user 376416 Jan 15 22:10 xas
-rw-rw-r-- 1 user user 347584 Jan 15 22:10 xat
-rw-rw-r-- 1 user user 376586 Jan 15 22:10 xau
-rw-rw-r-- 1 user user 352778 Jan 15 22:10 xav
-rw-rw-r-- 1 user user 380608 Jan 15 22:10 xaw
-rw-rw-r-- 1 user user 356634 Jan 15 22:10 xax
-rw-rw-r-- 1 user user 377414 Jan 15 22:10 xay
-rw-rw-r-- 1 user user 346342 Jan 15 22:10 xaz

切分为1MB的文件

可以使用-b参数,切分为准确字节的文件,如下:

1
2
3
4
5
6
7
8
9
10
$ split aa -b 1024000
$ ll
-rw-rw-r-- 1 user user 611037792 Jan 15 22:09 aa
-rw-rw-r-- 1 user user 1024000 Jan 15 22:15 xaa
-rw-rw-r-- 1 user user 1024000 Jan 15 22:15 xab
-rw-rw-r-- 1 user user 1024000 Jan 15 22:15 xac
-rw-rw-r-- 1 user user 1024000 Jan 15 22:15 xad
-rw-rw-r-- 1 user user 1024000 Jan 15 22:15 xae
-rw-rw-r-- 1 user user 1024000 Jan 15 22:15 xaf

指定前缀

这个参数直接跟在输入的文件后面即可,如下:

1
2
3
4
5
6
7
8
9
10
$ split aa DAT
$ ls
aa
DATaa
DATab
DATac
DATad
DATae
DATaf

Git分支

使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作。
在很多版本控制系统中,这是个昂贵的过程,常常需要创建一个源代码目录的完整副本,对大型项目来说会花费很长时间。

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

何谓分支

Git与其他版本管理的区别在于,Git保存的不是文件差异或者变化量,而是一系列的文件快照。
而在git中所谓的分支就是一个40个字符长度的SHA-1表示的一个指针。

Git 中的分支,其实本质上仅仅是个指向 commit 对象的可变指针。Git 会使用 master 作为分支的默认名字。在若干次提交后,你其实已经有了一个指向最后一次提交对象的 master 分支,它在每次提交的时候都会自动向前移动。

那么,Git 是如何知道你当前在哪个分支上工作的呢?其实答案也很简单,它保存着一个名为 HEAD 的特别指针,在 Git 中,它是一个指向你正在工作中的本地分支的指针(译注:将 HEAD 想象为当前分支的别名。)。运行 git branch 命令,仅仅是建立了一个新的分支,但不会自动切换到这个分支中去,除非你使用checkout切换到另外一个分支中。

分支简介

为了真正理解 Git 处理分支的方式,我们需要回顾一下 Git 是如何保存数据的。

或许你还记得 起步 的内容, Git 保存的不是文件的变化或者差异,而是一系列不同时刻的 快照

在进行提交操作时,Git 会保存一个提交对象(commit object)。 知道了 Git 保存数据的方式,我们可以很自然的想到——该提交对象会包含一个指向暂存内容快照的指针。 但不仅仅是这样,该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。 首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象, 而由多个分支合并产生的提交对象有多个父对象,

为了更加形象地说明,我们假设现在有一个工作目录,里面包含了三个将要被暂存和提交的文件。 暂存操作会为每一个文件计算校验和(使用我们在 起步 中提到的 SHA-1 哈希算法),然后会把当前版本的文件快照保存到 Git 仓库中 (Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交:

1
2
$ git add README test.rb LICENSE
$ git commit -m 'The initial commit of my project'

当使用 git commit 进行提交操作时,Git 会先计算每一个子目录(本例中只有项目根目录)的校验和, 然后在 Git 仓库中这些校验和保存为树对象。随后,Git 便会创建一个提交对象, 它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。 如此一来,Git 就可以在需要的时候重现此次保存的快照。

现在,Git 仓库中有五个对象:三个 blob 对象(保存着文件快照)、一个 对象 (记录着目录结构和 blob 对象索引)以及一个 提交 对象(包含着指向前述树对象的指针和所有提交信息)。

首次提交对象及其树结构。

做些修改后再次提交,那么这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针。

提交对象及其父对象。

Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master。 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master 分支。 master 分支会在每次提交时自动向前移动。

分支及其提交历史。

分支创建

Git 是怎么创建新分支的呢? 很简单,它只是为你创建了一个可以移动的新的指针。 比如,创建一个 testing 分支, 你需要使用 git branch 命令:

1
$ git branch testing

这会在当前所在的提交对象上创建一个指针。

两个指向相同提交历史的分支。

那么,Git 又是怎么知道当前在哪一个分支上呢? 也很简单,它有一个名为 HEAD 的特殊指针。 请注意它和许多其它版本控制系统(如 Subversion 或 CVS)里的 HEAD 概念完全不同。 在 Git 中,它是一个指针,指向当前所在的本地分支(译注:将 HEAD 想象为当前分支的别名)。 在本例中,你仍然在 master 分支上。 因为 git branch 命令仅仅 创建 一个新分支,并不会自动切换到新分支中去。

HEAD 指向当前所在的分支。

你可以简单地使用 git log 命令查看各个分支当前所指的对象。 提供这一功能的参数是 --decorate

1
2
3
4
$ git log --oneline --decorate
f30ab (HEAD -> master, testing) add feature #32 - ability to add new formats to the central interface
34ac2 Fixed bug #1328 - stack overflow under certain conditions
98ca9 The initial commit of my project

正如你所见,当前 mastertesting 分支均指向校验和以 f30ab 开头的提交对象。

分支切换

要切换到一个已存在的分支,你需要使用 git checkout 命令。 我们现在切换到新创建的 testing 分支去:

1
$ git checkout testing

这样 HEAD 就指向 testing 分支了。

HEAD 指向当前所在的分支。

那么,这样的实现方式会给我们带来什么好处呢? 现在不妨再提交一次:

1
2
$ vim test.rb
$ git commit -a -m 'made a change'

HEAD 分支随着提交操作自动向前移动。

如图所示,你的 testing 分支向前移动了,但是 master 分支却没有,它仍然指向运行 git checkout 时所指的对象。 这就有意思了,现在我们切换回 master 分支看看:

1
$ git checkout master

检出时 HEAD 随之移动。

这条命令做了两件事。 一是使 HEAD 指回 master 分支,二是将工作目录恢复成 master 分支所指向的快照内容。 也就是说,你现在做修改的话,项目将始于一个较旧的版本。 本质上来讲,这就是忽略 testing 分支所做的修改,以便于向另一个方向进行开发。

Note 分支切换会改变你工作目录中的文件在切换分支时,一定要注意你工作目录里的文件会被改变。 如果是切换到一个较旧的分支,你的工作目录会恢复到该分支最后一次提交时的样子。 如果 Git 不能干净利落地完成这个任务,它将禁止切换分支。

我们不妨再稍微做些修改并提交:

1
2
$ vim test.rb
$ git commit -a -m 'made other changes'

现在,这个项目的提交历史已经产生了分叉(参见 项目分叉历史)。 因为刚才你创建了一个新分支,并切换过去进行了一些工作,随后又切换回 master 分支进行了另外一些工作。 上述两次改动针对的是不同分支:你可以在不同分支间不断地来回切换和工作,并在时机成熟时将它们合并起来。 而所有这些工作,你需要的命令只有 branchcheckoutcommit

项目分叉历史。

你可以简单地使用 git log 命令查看分叉历史。 运行 git log --oneline --decorate --graph --all ,它会输出你的提交历史、各个分支的指向以及项目的分支分叉情况。

1
2
3
4
5
6
7
$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) made other changes
| * 87ab2 (testing) made a change
|/
* f30ab add feature #32 - ability to add new formats to the
* 34ac2 fixed bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project

由于 Git 的分支实质上仅是包含所指对象校验和(长度为 40 的 SHA-1 值字符串)的文件,所以它的创建和销毁都异常高效。 创建一个新分支就相当于往一个文件中写入 41 个字节(40 个字符和 1 个换行符),如此的简单能不快吗?

这与过去大多数版本控制系统形成了鲜明的对比,它们在创建分支时,将所有的项目文件都复制一遍,并保存到一个特定的目录。 完成这样繁琐的过程通常需要好几秒钟,有时甚至需要好几分钟。所需时间的长短,完全取决于项目的规模。 而在 Git 中,任何规模的项目都能在瞬间创建新分支。 同时,由于每次提交都会记录父对象,所以寻找恰当的合并基础(译注:即共同祖先)也是同样的简单和高效。 这些高效的特性使得 Git 鼓励开发人员频繁地创建和使用分支。

接下来,让我们看看你为什么应该这样做。

创建新分支的同时切换过去通常我们会在创建一个新分支后立即切换过去,这可以用 git checkout -b <newbranchname> 一条命令搞定。

分支的新建与合并

让我们来看一个简单的分支新建与分支合并的例子,实际工作中你可能会用到类似的工作流。 你将经历如下步骤:

  1. 开发某个网站。
  2. 为实现某个新的用户需求,创建一个分支。
  3. 在这个分支上开展工作。

正在此时,你突然接到一个电话说有个很严重的问题需要紧急修补。 你将按照如下方式来处理:

  1. 切换到你的线上分支(production branch)。
  2. 为这个紧急任务新建一个分支,并在其中修复它。
  3. 在测试通过之后,切换回线上分支,然后合并这个修补分支,最后将改动推送到线上分支。
  4. 切换回你最初工作的分支上,继续工作。

新建分支

首先,我们假设你正在你的项目上工作,并且在 master 分支上已经有了一些提交。

一个简单的提交历史。

现在,你已经决定要解决你的公司使用的问题追踪系统中的 #53 问题。 想要新建一个分支并同时切换到那个分支上,你可以运行一个带有 -b 参数的 git checkout 命令:

1
2
$ git checkout -b iss53
Switched to a new branch "iss53"

它是下面两条命令的简写:

1
2
$ git branch iss53
$ git checkout iss53

创建一个新分支指针。

你继续在 #53 问题上工作,并且做了一些提交。 在此过程中,iss53 分支在不断的向前推进,因为你已经检出到该分支 (也就是说,你的 HEAD 指针指向了 iss53 分支)

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

`iss53` 分支随着工作的进展向前推进。

现在你接到那个电话,有个紧急问题等待你来解决。 有了 Git 的帮助,你不必把这个紧急问题和 iss53 的修改混在一起, 你也不需要花大力气来还原关于 53# 问题的修改,然后再添加关于这个紧急问题的修改,最后将这个修改提交到线上分支。 你所要做的仅仅是切换回 master 分支。

但是,在你这么做之前,要留意你的工作目录和暂存区里那些还没有被提交的修改, 它可能会和你即将检出的分支产生冲突从而阻止 Git 切换到该分支。 最好的方法是,在你切换分支之前,保持好一个干净的状态。 有一些方法可以绕过这个问题(即,暂存(stashing) 和 修补提交(commit amending)), 我们会在 贮藏与清理 中看到关于这两个命令的介绍。 现在,我们假设你已经把你的修改全部提交了,这时你可以切换回 master 分支了:

1
2
$ git checkout master
Switched to branch 'master'

这个时候,你的工作目录和你在开始 #53 问题之前一模一样,现在你可以专心修复紧急问题了。 请牢记:当你切换分支的时候,Git 会重置你的工作目录,使其看起来像回到了你在那个分支上最后一次提交的样子。 Git 会自动添加、删除、修改文件以确保此时你的工作目录和这个分支最后一次提交时的样子一模一样。

接下来,你要修复这个紧急问题。 我们来建立一个 hotfix 分支,在该分支上工作直到问题解决:

1
2
3
4
5
6
$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'fixed the broken email address'
[hotfix 1fb7853] fixed the broken email address
1 file changed, 2 insertions(+)

基于 `master` 分支的紧急问题分支(hotfix branch)。

你可以运行你的测试,确保你的修改是正确的,然后将 hotfix 分支合并回你的 master 分支来部署到线上。 你可以使用 git merge 命令来达到上述目的:

1
2
3
4
5
6
$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
index.html | 2 ++
1 file changed, 2 insertions(+)

在合并的时候,你应该注意到了“快进(fast-forward)”这个词。 由于你想要合并的分支 hotfix 所指向的提交 C4 是你所在的提交 C2 的直接后继, 因此 Git 会直接将指针向前移动。换句话说,当你试图合并两个分支时, 如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候, 只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。

现在,最新的修改已经在 master 分支所指向的提交快照中,你可以着手发布该修复了。

`master` 被快进到 `hotfix`。

关于这个紧急问题的解决方案发布之后,你准备回到被打断之前时的工作中。 然而,你应该先删除 hotfix 分支,因为你已经不再需要它了 —— master 分支已经指向了同一个位置。 你可以使用带 -d 选项的 git branch 命令来删除分支:

1
2
$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

现在你可以切换回你正在工作的分支继续你的工作,也就是针对 #53 问题的那个分支(iss53 分支)。

1
2
3
4
5
6
$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'finished the new footer [issue 53]'
[iss53 ad82d7a] finished the new footer [issue 53]
1 file changed, 1 insertion(+)

继续在 `iss53` 分支上的工作。

你在 hotfix 分支上所做的工作并没有包含到 iss53 分支中。 如果你需要拉取 hotfix 所做的修改,你可以使用 git merge master 命令将 master 分支合并入 iss53 分支,或者你也可以等到 iss53 分支完成其使命,再将其合并回 master 分支。

分支的合并

假设你已经修正了 #53 问题,并且打算将你的工作合并入 master 分支。 为此,你需要合并 iss53 分支到 master 分支,这和之前你合并 hotfix 分支所做的工作差不多。 你只需要检出到你想合并入的分支,然后运行 git merge 命令:

1
2
3
4
5
6
$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html | 1 +
1 file changed, 1 insertion(+)

这和你之前合并 hotfix 分支的时候看起来有一点不一样。 在这种情况下,你的开发历史从一个更早的地方开始分叉开来(diverged)。 因为,master 分支所在提交并不是 iss53 分支所在提交的直接祖先,Git 不得不做一些额外的工作。 出现这种情况的时候,Git 会使用两个分支的末端所指的快照(C4C5)以及这两个分支的公共祖先(C2),做一个简单的三方合并。

一次典型合并中所用到的三个快照。

和之前将分支指针向前推进所不同的是,Git 将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它。 这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。

一个合并提交。

Figure 25. 一个合并提交

既然你的修改已经合并进来了,就不再需要 iss53 分支了。 现在你可以在任务追踪系统中关闭此项任务,并删除这个分支。

1
$ git branch -d iss53

遇到冲突时的分支合并

有时候合并操作不会如此顺利。 如果你在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git 就没法干净的合并它们。 如果你对 #53 问题的修改和有关 hotfix 分支的修改都涉及到同一个文件的同一处,在合并它们的时候就会产生合并冲突:

1
2
3
4
$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

此时 Git 做了合并,但是没有自动地创建一个新的合并提交。 Git 会暂停下来,等待你去解决合并产生的冲突。 你可以在合并冲突后的任意时刻使用 git status 命令来查看那些因包含合并冲突而处于未合并(unmerged)状态的文件:

1
2
3
4
5
6
7
8
9
10
11
$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")

Unmerged paths:
(use "git add <file>..." to mark resolution)

both modified: index.html

no changes added to commit (use "git add" and/or "git commit -a")

任何因包含合并冲突而有待解决的文件,都会以未合并状态标识出来。 Git 会在有冲突的文件中加入标准的冲突解决标记,这样你可以打开这些包含冲突的文件然后手动解决冲突。 出现冲突的文件会包含一些特殊区段,看起来像下面这个样子:

1
2
3
4
5
6
7
<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

这表示 HEAD 所指示的版本(也就是你的 master 分支所在的位置,因为你在运行 merge 命令的时候已经检出到了这个分支)在这个区段的上半部分(======= 的上半部分),而 iss53 分支所指示的版本在 ======= 的下半部分。 为了解决冲突,你必须选择使用由 ======= 分割的两部分中的一个,或者你也可以自行合并这些内容。 例如,你可以通过把这段内容换成下面的样子来解决冲突:

1
2
3
<div id="footer">
please contact us at email.support@github.com
</div>

上述的冲突解决方案仅保留了其中一个分支的修改,并且 <<<<<<< , ======= , 和 >>>>>>> 这些行被完全删除了。 在你解决了所有文件里的冲突之后,对每个文件使用 git add 命令来将其标记为冲突已解决。 一旦暂存这些原本有冲突的文件,Git 就会将它们标记为冲突已解决。

如果你想使用图形化工具来解决冲突,你可以运行 git mergetool,该命令会为你启动一个合适的可视化合并工具,并带领你一步一步解决这些冲突:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
{local}: modified file
{remote}: modified file
Hit return to start merge resolution tool (opendiff):

如果你想使用除默认工具(在这里 Git 使用 opendiff 做为默认的合并工具,因为作者在 Mac 上运行该程序) 外的其他合并工具,你可以在 “下列工具中(one of the following tools)” 这句后面看到所有支持的合并工具。 然后输入你喜欢的工具名字就可以了。

Note 如果你需要更加高级的工具来解决复杂的合并冲突,我们会在 高级合并 介绍更多关于分支合并的内容。

等你退出合并工具之后,Git 会询问刚才的合并是否成功。 如果你回答是,Git 会暂存那些文件以表明冲突已解决: 你可以再次运行 git status 来确认所有的合并冲突都已被解决:

1
2
3
4
5
6
7
8
$ git status
On branch master
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)

Changes to be committed:

modified: index.html

新建分支

新建分支并切换到该分支的命令:

1
git checkout -b new-branch-name

相当于下面的两条命令

1
2
git branch new-branch-name
git checkout new-branch-name

在切换分支的时候需要留心暂存区或者工作目录里,那些还没有提交的修改,它会和你即将检出的分支产生冲突从而阻止 Git 为你切换分支。切换分支的时候最好保持一个清洁的工作区域。

删除分支

在分支完成开发后,可以使用参数d来删除该分支

1
git branch --delete new-branch-name

有时由于一些分支中包含没有被合并的修改,简单地使用上述命令可能会提示分支删除错误,此时可以使用-D来强制删除改分支。

合并分支

首先切换到master分支,然后合并修改的分支

1
2
git checkout master
git merge new-branch-name

在合并的时候,你应该注意到了“快进(fast-forward)”这个词。 由于你想要合并的分支 hotfix 所指向的提交 C4 是你所在的提交 C2 的直接后继, 因此 Git 会直接将指针向前移动。换句话说,当你试图合并两个分支时, 如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候, 只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。

关于这个紧急问题的解决方案发布之后,你准备回到被打断之前时的工作中。 然而,你应该先删除 hotfix 分支,因为你已经不再需要它了 —— master 分支已经指向了同一个位置。 你可以使用带 -d 选项的 git branch 命令来删除分支:

1
2
$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

遇到冲突时的分支合并

如果你想用一个有图形界面的工具来解决这些问题,不妨运行 git mergetool,它会调用一个可视化的合并工具并引导你解决所有冲突。

显示分支信息

默认情况下,使用git branch会显示目前分支信息,如果加上-v参数,则会显示每一个分支的最后一次提交信息。

如果使用git branch -a会显示所有的分支。

远程分支

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

获取所有远程分支

1
2
3
git branch -r | grep -v '\->' | while read remote; do git branch --track "${remote#origin/}" "$remote"; done
git fetch --all
git pull --all

推送本地分支

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

1
git push (远程仓库名) (分支名)

删除远程分支

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

1
git push origin :branch-name

或者使用下面比较简单的命令:

1
git push origin --delete branch-name

分支的衍合

把一个分支中的修改整合到另一个分支的办法有两种:mergerebase

使用方法为:

1
2
git checkout master
git merge branch-name
1
2
git checkout branch-name
git rebase master

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

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

跟踪分支

从一个远程跟踪分支检出一个本地分支会自动创建所谓的“跟踪分支”(它跟踪的分支叫做“上游分支”)。 跟踪分支是与远程分支有直接关系的本地分支。 如果在一个跟踪分支上输入 git pull,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。

当克隆一个仓库时,它通常会自动地创建一个跟踪 origin/master 的 master 分支。 然而,如果你愿意的话可以设置其他的跟踪分支,或是一个在其他远程仓库上的跟踪分支,又或者不跟踪 master 分支。 最简单的实例就是像之前看到的那样,运行 git checkout -b /。 这是一个十分常用的操作所以 Git 提供了 –track 快捷方式:

1
2
3
$ git checkout --track origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'

分支开发工作流

长期分支

因为 Git 使用简单的三方合并,所以就算在一段较长的时间内,反复把一个分支合并入另一个分支,也不是什么难事。 也就是说,在整个项目开发周期的不同阶段,你可以同时拥有多个开放的分支;你可以定期地把某些主题分支合并入其他分支中。

许多使用 Git 的开发者都喜欢使用这种方式来工作,比如只在 master 分支上保留完全稳定的代码——有可能仅仅是已经发布或即将发布的代码。 他们还有一些名为 develop 或者 next 的平行分支,被用来做后续开发或者测试稳定性——这些分支不必保持绝对稳定,但是一旦达到稳定状态,它们就可以被合并入 master 分支了。 这样,在确保这些已完成的主题分支(短期分支,比如之前的 iss53 分支)能够通过所有测试,并且不会引入更多 bug 之后,就可以合并入主干分支中,等待下一次的发布。

事实上我们刚才讨论的,是随着你的提交而不断右移的指针。 稳定分支的指针总是在提交历史中落后一大截,而前沿分支的指针往往比较靠前。

趋于稳定分支的线性图。

通常把他们想象成流水线(work silos)可能更好理解一点,那些经过测试考验的提交会被遴选到更加稳定的流水线上去。

趋于稳定分支的工作流(“silo”)视图。

Figure 27. 趋于稳定分支的流水线(“silo”)视图

你可以用这种方法维护不同层次的稳定性。 一些大型项目还有一个 proposed(建议) 或 pu: proposed updates(建议更新)分支,它可能因包含一些不成熟的内容而不能进入 next 或者 master 分支。 这么做的目的是使你的分支具有不同级别的稳定性;当它们具有一定程度的稳定性后,再把它们合并入具有更高级别稳定性的分支中。 再次强调一下,使用多个长期分支的方法并非必要,但是这么做通常很有帮助,尤其是当你在一个非常庞大或者复杂的项目中工作时。

主题分支

主题分支对任何规模的项目都适用。 主题分支是一种短期分支,它被用来实现单一特性或其相关工作。 也许你从来没有在其他的版本控制系统(VCS)上这么做过,因为在那些版本控制系统中创建和合并分支通常很费劲。 然而,在 Git 中一天之内多次创建、使用、合并、删除分支都很常见。

你已经在上一节中你创建的 iss53hotfix 主题分支中看到过这种用法。 你在上一节用到的主题分支(iss53hotfix 分支)中提交了一些更新,并且在它们合并入主干分支之后,你又删除了它们。 这项技术能使你快速并且完整地进行上下文切换(context-switch)——因为你的工作被分散到不同的流水线中,在不同的流水线中每个分支都仅与其目标特性相关,因此,在做代码审查之类的工作的时候就能更加容易地看出你做了哪些改动。 你可以把做出的改动在主题分支中保留几分钟、几天甚至几个月,等它们成熟之后再合并,而不用在乎它们建立的顺序或工作进度。

考虑这样一个例子,你在 master 分支上工作到 C1,这时为了解决一个问题而新建 iss91 分支,在 iss91 分支上工作到 C4,然而对于那个问题你又有了新的想法,于是你再新建一个 iss91v2 分支试图用另一种方法解决那个问题,接着你回到 master 分支工作了一会儿,你又冒出了一个不太确定的想法,你便在 C10 的时候新建一个 dumbidea 分支,并在上面做些实验。 你的提交历史看起来像下面这个样子:

拥有多个主题分支的提交历史。

Figure 28. 拥有多个主题分支的提交历史

现在,我们假设两件事情:你决定使用第二个方案来解决那个问题,即使用在 iss91v2 分支中方案。 另外,你将 dumbidea 分支拿给你的同事看过之后,结果发现这是个惊人之举。 这时你可以抛弃 iss91 分支(即丢弃 C5C6 提交),然后把另外两个分支合并入主干分支。 最终你的提交历史看起来像下面这个样子:

合并了 `dumbidea` 和 `iss91v2` 分支之后的提交历史。

Figure 29. 合并了 dumbideaiss91v2 分支之后的提交历史

我们将会在 分布式 Git 中向你揭示更多有关分支工作流的细节, 因此,请确保你阅读完那个章节之后,再来决定你的下个项目要使用什么样的分支策略(branching scheme)。

请牢记,当你做这么多操作的时候,这些分支全部都存于本地。 当你新建和合并分支的时候,所有这一切都只发生在你本地的 Git 版本库中 —— 没有与服务器发生交互。

二十几岁比别人幸运,就靠这9个好习惯

习惯一旦养成,就会成为支配人生的一种行为力量,它可以主宰人的一生。

  • 播下一种行为,收获一种习惯;
  • 播下一种习惯,收获一种性格;
  • 播下一种性格,收获一种命运;
  1. 树立目标并做好规划,目标要打,并专心致志,制定一个完成目标的可行性计划,并开始行动;
  2. 塑造一种可以战胜一切的品质,品格是人生的王冠和荣耀,记住有志者,事竟成,对别人的宽容就是对自己的宽容,要有责任感,成功源于细节;
  3. 了解各种为人处世的细节,敢于怀疑才会有进步,感恩的心会使人变得愉悦和健康,消除嫉妒心,把幸福分给别人,幸福才会更多,你付出什么,就会得到什么,换位思考,学会理解,尊重他人才能包容一切
  4. 调节好情绪与心态,控制好你自己的情绪,记住,情绪成就一切,你的心态决定生活状态,保持开朗,打开心灵之窗,,正确对待人生的各种得失,学会冷静,会调节与释放压力,心有多大,舞台就有多大
  5. 学会高效工作,学会规划自己的时间,确定工作的优先次序,切忌三心二意,有了计划要马上付诸行动,好的习惯是提高效率的关键
  6. 正确支配自己的财富
  7. 开发自己的创造力,留住刹那间的思维火花,
  8. 捕捉每一个属于自己的机会,,机遇只垂青有准备的人
  9. 努力找寻人生的真谛,学会从不同的角度看问题,生活的不如意大多源自于比较,平静是一种难得的幸福,时常与自己的心灵说说话,推荐打坐,嘿嘿