本文是学习Linux命令 (Learn linux command)系列文章的第二篇,在这里会介绍一些让大家平时都会经常用到的命令。注意,命令出现的顺序与重要程度无关。

echo - display a line of text

echo命令是一个shell内置命令,但是你往往可以在系统上找到独立的echo程序,例如我的系统上echo位于/usr/bin下:

[kodango@devops ~]$ which echo
/usr/bin/echo

但是这并不妨碍我们把它当成一个内置命令来对待

echo命令可以用于简单的文本打印:

[kodango@devops ~]$ echo "hello world"
hello world

但是有一点需要小心得是,在打印的字符串之外最好用引号引起来,避免被shell展开,例如常见的星号会被用于文件名展开:

[kodango@devops ~]$ echo *
helloworld share workspace
[kodango@devops ~]$ echo "*"
*

默认情况下,echo在输出时会加上回车换行符,但是有时候我们可能不需要它,这时可以通过-n选项来避免\n的打印:

[kodango@devops ~]$ echo -n "hello world"
hello world[kodango@devops ~]

有时候,你可能会习惯怀地在打印的字符串中添加\n等转义字符,但是会发现它们并没有在结果中被转义打印出来:

[kodango@devops ~]$ echo -n "hello world\n"
hello world\n

唯一的解释是,在默认情况下,echo命令并不会解释转义字符。实际上,你需要通过-e选项来主动打开这个功能:

[kodango@devops ~]$ echo -ne "hello world\n"
hello world
[kodango@devops ~]

通过转义字符,在echo显示的时候还能加上颜色,例如:

[kodango@devops ~]$ echo -e "\e[0;32mhello, world\e[0m"
hello, world

echo支持为数不多的转义字符,具体可以查看help的帮助,记住不要用echo --help,而是help echo或者man echo

cat - concatenate files and print on the standard output

cat命令的作用是将文件或者标准输入的内容,拼在一起打印到标准输出。利用cat命令和here document,我们还可以将多行内容写入到文件中:

[kodango@devops shell_temp]$ cat << EOF >> output.txt
> line 1
> line 2
> EOF

这里的EOF是here document的起始和结束的标记,只要两者相同即可。

通过-n选项可以在输出文件内容时显示行号:

[kodango@devops shell_temp]$ cat output.txt -n
     1	line 1
     2	line 2

或者你也可以利用nl命令来显示行号:

[kodango@devops shell_temp]$ cat output.txt | nl
     1	line 1
     2	line 2

默认情况下, cat命令不会显示控制字符或者Tab字符等,导致终端上会有些肉眼下看不见的东西,有时候会出现莫名其妙的问题。

这个时候可以使用cat的几个选项来分别显示这些字符:-E(显示行末标记,$),-v(显示控制字符,但是不包括LFD和TAB),-T(显示Tab为^I)。同时cat命令还提供了-A选项用于显示所有不可见的字符,这个选项相当于-vET的全体:

[kodango@devops ~]$ ls --color | cat -A
^[[0m^[[01;34mhelloworld^[[0m$
^[[01;34mshare^[[0m$
^[[01;34mworkspace^[[0m$

printf - format and print data

printf命令和echo命令类似,它们都是用于打印字符串。但是相比之下,printf命令还可以用于格式化输出的内容,同其它大多数编程语言中的printf/print功能类似,比较符号码农的思维。

熟悉标准printf用法的同学,不会对它陌生。它的用法也是很简单:printf [-v var] format [arguments]。例如:

[kodango@devops ~]$ printf "%s, %s\n" hello world
hello, world

上面第一个参数是格式化字符串,后面的参数是占位符(如%s)对应的实际参数。

printf唯一的命令行选项-v,它后面指定一个变量名,作用是将printf的输出就保存到变量中。例如,在简洁的Bash编程技巧一文中介绍过的,利用printf命令将数字转换成其十六进制形式:

[kodango@devops ~]$ printf -v var '%x' 111
[kodango@devops ~]$ echo $var
6f

logdotsh中也大量用到了printf命令来格式化日志输出,有兴趣的同学可以看看。

sort - sort lines of text files

在校期间,接触最多的算法应该是排序算法了,例如冒泡排序、插入排序、堆排序等等。在Linux系统下,你就不必为排序发愁了,因为它默认就提供了sort命令用于排序,而且大多数情况下,它的性能应该比你自己实现的要好。

我们从最简单的sort命令开始,我们有一个例子文件,它的内容如下所示:

[kodango@devops ~]$ cat /tmp/line 
userage
user_name
user_id
USER_ID

现在用sort命令来排序:

[kodango@devops ~]$ sort /tmp/line
userage
user_id
USER_ID
user_name

上面的排序有问题吗?细心的同学会发现,字母a的ascii值为97,而字符'_'的ascii值为95,按照字节顺序排序的话,userage理应排在user_id的后面。还有USER_ID和user_id的顺序也有问题,在ascii表中大写字母是排在小写字母之前的,所以USER_ID排序的结果应该在user_id之前。

为什么会出现这样的问题呢?原因是排序的过程会受到系统locale的影响,不同的locale排序的结果是不一样的,这点在sort命令的帮助最后也是有提及的:

*** WARNING ***
The locale specified by the environment affects sort order.
Set LC_ALL=C to get the traditional sort order that uses
native byte values.

好我们按照帮助的提示,使用LC_ALL=C来明确表示按照严格的字符顺序排序,结果就正常了:

[kodango@devops ~]$ LC_ALL=C sort /tmp/line 
USER_ID
user_id
user_name
userage

在locale中,影响排序的环境变量是LC_COLLATE,但是如果系统设置了LC_ALL的话,会覆盖单独的LC_*环境变量设置,所以最好还是使用LC_ALL来限制。

默认情况下,sort排序过程中大小写是区分出来的,这一点从上面的例子中也可以看出。我们可以利用--ignore-case(-f)选项来不区分大小写,比较下面两个命令之间的区别:

[kodango@devops ~]$ echo -e "d\nD\nc\nb\nB\na" | LC_COLLATE=C sort
B
D
a
b
c
d
[kodango@devops ~]$ echo -e "d\nD\nc\nb\nB\na" | LC_COLLATE=C sort --ignore-case
a
B
b
c
D
d

从第二个结果中,可以看出默认情况下sort命令的排序是不稳定的,何谓稳定?稳定的定义是指,相同的记录在排序前后彼此的顺序保持一致。在上例的第二个结果中,d和D的顺序是不一致的。

如果要获得稳定的排序,可以加上--stable选项:

[kodango@devops ~]$ echo -e "d\nD\nc\nb\nB\na" | LC_COLLATE=C sort --ignore-case --stable
a
b
B
c
d
D

sort命令默认的排序规则是按照字典顺序(选项-d)从小到大排序。如果针对数字排序,可以指定选项-n;如果针对浮点数排序,可以指定选项-g;如果要从大到小排序,可以指定选项-r。例如:

[kodango@devops ~]$ echo -e "1.2\n1.1" | sort -gr
1.2
1.1

sort命令还可以进一步地按照字段排序,字段之间的分隔符可以通过选项-t指定,默认是空格。排序的字段选择可以通过-k, --key=start[,stop]选项控制,表示排序的key是从字段start开始一直到字段stop结束之间的内容,如果不指定stop,则默认到记录的结尾。start和stop的值形式为F[.C][OPTS],其中F表示字段的序号,C表示字段中字符的位置,合起来就是第F个字段的第C个字符,两者的值都从1开始计算。OPTS表示排序的开关,即上面介绍的-n/-d/-g/-r等等,它们会覆盖全局的选项设置。

例如对/etc/passwd按照用户名排序,用户名是第一个字段,字段分隔符为冒号(:),命令如下所示:

[kodango@devops ~]$ sort -t: -k1,1 /etc/passwd | head -2
avahi:x:84:84:avahi:/:/bin/false
bin:x:1:1:bin:/bin:/bin/false

按照用户id的大小逆序排序:

[kodango@devops ~]$ sort -t: -k3nr,3 /etc/passwd | head -2
kodango:x:1000:1000::/home/kodango:/bin/bash
git:x:999:998:git daemon user:/:/bin/bash

sort命令可以利用-u选项去除重复行:

[kodango@devops ~]$ echo -ne '1\n1\n2\n' | sort -u
1
2

最后说一下sort命令使用上一个比较容易出错的地方,有时候我们会希望将文件排序的结果写到相同的文件中,自然而然就想到利用重定向功能:

[kodango@devops ~]$ sort line > line
[kodango@devops ~]$ cat line
[kodango@devops ~]$ 

但是结果却发现文件变成空了,这是因为在排序之前,首先会打开重定向的文件,输出重定向会打开文件并清空原有的内容,所以导致line文件变成空了。

幸运地是,sort命令提供了-o选项用于in-place排序,这样就可以让排序文件与输出文件是同一个了:

[kodango@devops ~]$ sort -o line line
[kodango@devops ~]$ cat line
1
2
3
4
5

paste - merge lines of files

paste命令用于合并多个文件的行,即将每个文件的同一行按照特定的分隔符合并成一行。例如:

[kodango@devops ~]$ cat name
name
kodango
[kodango@devops ~]$ cat age
age
99
[kodango@devops ~]$ paste name age
name	age
kodango	99

默认使用Tab分隔,可以通过-d选项指定其它分隔符:

[kodango@devops ~]$ paste -d'|' name age
name|age
kodango|99

paste命令还可以合并单个文件的行,这个用法很新奇:

[kodango@devops ~]$ paste -sd, name
name,kodango

是不是给你一种新的拼接文件行的思路呢。

cut - print selected parts of lines

cut命令用于去除一行中的某些字段,仅打印剩余的字段,或者说剩余的几列。字段之间的分隔符可以由-d选项指定,例如,打印/etc/passwd文件的第一列:

[kodango@devops ~]$ cut -d: -f1 /etc/passwd | head -2
root
bin

字段的选择可以通过-f选项控制,它的取值可以为:

  • N: 指定第N个字段;
  • N-: 指定从第N个字段开始到最后一个字段;
  • N-M: 指定从第N个字段开始到第M个字段结束;
  • -M: 指定从第1个字段开始到第M个字段结束;

字段的序号从1开始计算,例如:

[kodango@devops ~]$ cut -d: -f1-3 /etc/passwd | head -1
root:x:0
[kodango@devops ~]$ cut -d: -f-3 /etc/passwd | head -1
root:x:0
[kodango@devops ~]$ cut -d: -f4- /etc/passwd | head -1
0:root:/root:/bin/bash

除按字段控制之外,cut还支持以字符为单位来选择,这是通过-c选项来完成的:

[kodango@devops ~]$ cut -c1-2 /etc/passwd | head -1
ro

wc - print newline, word, and byte counts for each file

wc命令用于统计文件的行数、单词个数、字符数以及字节数等数据。

a. 显示文件/etc/vimrc的行数:

[kodango@devops ~]$ wc -l /etc/vimrc 
16 /etc/vimrc

输出结果为行数,后面跟着文件名。如果只想获得行数可以用cat /etc/vimrc | wc -l

这里有一点要注意的是,行数的统计是以回车\n的个数为准的,所以有时候统计的结果可能与你认为的不一样:

[kodango@devops ~]$ echo -ne "line1\nline2" | wc -l
1

b. 统计/etc/vimrc文件出现的单词个数

[kodango@devops ~]$ wc -w /etc/vimrc
131 /etc/vimrc

单词之间使用空格分隔。

c. 统计/etc/vimrc文件的字符数

[kodango@devops ~]$ wc -m /etc/vimrc
841 /etc/vimrc

或者

[kodango@devops ~]$ wc -c /etc/vimrc
841 /etc/vimrc

head - output the first part of files

head命令用于显示文件的头n个字节(-c)或者头n行(-n)。

例如显示文件的前24个字节:

[kodango@devops ~]$ head -c 24 ~/.bashrc
#
# Kodango's ~/.bashrc

显示文件的前3行:

[kodango@devops ~]$ head -n 3 ~/.bashrc
#
# Kodango's ~/.bashrc
#

tail - output the last part of files

tail命令与head命令是一对,它们的作用是相反的,用法也大致差不多。例如通过-n 3选项显示最后3行:

[kodango@devops ~]$ sudo tail -n 3 /var/log/php-fpm.log 
[03-Jan-2013 00:47:46] NOTICE: exiting, bye-bye!
[05-Jan-2013 20:57:13] NOTICE: fpm is running, pid 257
[05-Jan-2013 20:57:13] NOTICE: ready to handle connections

但是tail最实用的选项是-f,当一个文件的内容增长时,可以实时地显示文件结尾处的内容,在观察日志时非常有用:

[kodango@devops ~]$ tail -f xxx.log

comm - compare two sorted files line by line

comm用于比较两个排序好的文件,返回的结果有三列,第一列是只有在文件A中有的行,第二列是只有在文件B中有的行,第三列则是两个文件共有的行:

$ comm test.txt test2.txt                
line 1 
	line 11
		line 2

要得到最初要求的结果,则只需要取相应的列就可以了。comm命令非常人性化地考虑到这个需求:

$ comm test.txt test2.txt -1 -2
line 2
$ comm test.txt test2.txt -2 -3
line 1 
$ comm test.txt test2.txt -1 -3
line 11

其中,-1, -2与-3这个参数分别表示不输出第1、2或者3列。