概述

GUI

远程登录

文件和目录管理

Linux文件系统

文件与目录的权限

文件属性

文档的压缩与打包

后缀 类型
gz 由gzip压缩的文件
bz2 由bzip2压缩的文件
xz 由xz压缩的文件
tar 由tar程序打包的文件(tar并没有压缩功能, 只是把一个目录合并成一个文件)
tar.gz 先经tar程序打包,再经gzip压缩
tar.bz2 先经tar程序打包,再经bzip2压缩
tar.xz 先经tar程序打包,再经xz压缩

gzip

  • -c :将压缩的数据输出到屏幕上,可通过重定向来处理;
  • -d :解压缩的参数;
  • -t :可以用来检验一个压缩档的一致性;
  • -v :可以显示出原文件 / 压缩文件的压缩比等;
  • -k : 不删除原文件(默认会删除原文件)
  • -数字 :压缩等级,-1 最快,但是压缩比最差、-9 最慢,但是压缩比 最好!默认是 -6

bzip2

bzip 2 比 gzip 提供更佳的压缩比.

  • -c :将压缩的数据输出到屏幕上,可通过重定向来处理;
  • -d :解压缩的参数;
  • -t :可以用来检验一个压缩档的一致性;
  • -v :可以显示出原文件 / 压缩文件的压缩比等;
  • -k : 不删除原文件(默认会删除原文件)
  • -数字 :压缩等级,-1 最快,但是压缩比最差、-9 最慢,但是压缩比 最好!默认是 -6

xz

  • -d :解压缩的参数;
  • -v :可以显示出原文件 / 压缩文件的压缩比等;
  • -k : 不删除原文件(默认会删除原文件)
  • -z :压缩的参数

tar

用来建立,还原备份文件的工具程序,可以将多个目录或文件打包成一个大文件,同时还可以使用 gzip/bzip2/xz 将该文件同时进行压缩。常用的参数有:

  • -c或–create 建立新的备份文件。
  • -f<备份文件>或–file=<备份文件> 指定备份文件,可以搭配 -C ( 大写 ) 在特定目录解开。
  • -v或–verbose 显示指令执行过程。
  • -t 查看tar包里的文件
  • -x或–extract或–get 从备份文件中还原文件。
  • -z或–gzip或–ungzip 通过gzip指令处理备份文件。
  • -j 通过bzip2指令处理备份文件。
  • -J 通过xz指令处理备份文件。

zip/unzip

  • -r 递归处理,将指定目录下的所有文件和子目录一并处理。
1
2
3
$ zip 1.txt.zip 1.txt
$ zip -r test111.zip test111
$ unzip 1.txt.zip

文档压缩的练习

1、gzip, bzip2 能否直接压缩目录呢?

不能直接压缩目录

2、请快速写出,使用gzip和bzip2压缩和解压一个文件的命令。

1
2
3
4
$ gzip 1.txt
$ gzip -d 1.txt.gz
$ bzip2 1.txt
$ bzip2 -d 1.txt.bz2

3、tar 在打包的时候,如果想排除多个文件或者目录如何操作?

tar cvf 123.tar --exclude a.txt --exclude b.txt 123/

4、请实验,如果不加 “-” 是否正确, 如 tar zcvf 1.tar.gz 1.txt 2.txt ?

不加 - 一样没有问题

5、如何使用tar打包和解包 .tar.gz, .tar.bz2 的压缩包?

1
2
3
4
$ tar zcvf 1.tar.gz 1
$ tar zxvf 1.tar.gz
$ tar jcvf 1.tar.bz2 1
$ tar jxvf 1.tar.bz2

6、找一个大点的文件,使用tar 分别把这个文件打成 .tar.gz和.tar.bz2 压缩包,比较一下哪个包会更小,从而得出结论,是gzip压缩效果好还是bzip2压缩效果好?

理论上.tar.bz2的压缩包小一些,但个别时候,有相反的情况。但大多时候bzip2压缩效果好。

7、使用tar打包并压缩的时候,默认压缩级别为几? 想一想如何能够改变压缩级别呢?(提示,tar本身没有这个功能哦,可以尝试拆分打包和压缩)

tar打包压缩时,是按照gzip和bzip2的默认压缩级别来的,gzip工具默认压缩级别为6,bzip2默认压缩级别为9.

改变默认压缩级别可以这样来做,首先tar打包,然后再使用gzip或者bzip2压缩工具来压缩,压缩的时候指定压缩级别。如: tar cvf 1.tar 123/; gzip -2 1.tar

系统用户与用户组管理

用户

/etc/passwd

/etc/shadow

登录的用户

创建用户

修改密码

修改用户信息

删除用户

群组

/etc/group

/etc/gshadow

新增/删除群组

ACL

使用者身份切换

练习

1、查看配置文件/etc/shadow第一行中root账号的第三个字段(以’:'分隔)中的数字,请算一下这个数字是怎么来的?

距离19700101到修改密码的时间

2、写出一个您认为很强悍的密码.

3、查资料搞明白 /sbin/nologin 和 /bin/false 的区别,你知道他们用在什么场合吗?

  • /sbin/nologin: 不允许登录
  • /bin/false: 任何服务都不允许

4、请想一想,当我们创建一个新的账号时,系统会修改哪几个文件呢?

  • /etc/passwd
  • /etc/shadow
  • /etc/group
  • /etc/gshadow

5、假如我们已经创建了一个普通用户user1, 默认这个用户的家目录为/home/user1, 做实验证明能否直接修改/etc/passwd配置文件中user1的家目录那个字段而改变user1的家目录呢? (提示: 您可以使用 “cd ~ ”命令来进入当前用户家目录的方法来验证)

可以直接修改/etc/passwd配置文件中user1的家目录

6、/etc/passwd 文件以":"为分隔符,第三和第四个字段表示什么含义?如果把某一行的第三个字段改为’0’ 会发生什么?

如果修改为0,则表示该用户为超级用户

7、 先新增一个组group11,然后再新增一个账号user12, 使该账号所属组为刚刚新增的组。

1
2
groupadd group11
useradd –g group11 user12

-g主组, -G附属组

8、如果删除一个组时报错: “cannot remove the primary group of user ‘aming’” 这是什么意思?如何解决该问题呢?

因为待删除的组,是用户aming的主组,因此无法删除。 需要先将aming这个用户删除(或者将aming用户的主组修改为其他组)

9、如何删除某个账户时,连带这个账户的家目录一并删除?

1
userdel –r user0

10、普通账户可以修改自己的密码吗?

11、使用su时,后面加了 ‘-’ 表示什么含义?

12、sudo的作用是什么呢?

13、创建系统账号时,帐户名要符合什么样的规范?

磁盘管理

Linux下的磁盘

df

du

fdisk

mke2fs

mkfs

dumpe2fs

e2label

mount/umount

Shell编程

Shell基础

Shell程序(壳程序)的功能只是提供用户操作系统的一个接口。

命令(如ls, cp, mkdir等)都是独立的应用程序, 但是我们可以通过shell程序 (就是命令列模式) 来操作这些应用程序,让这些应用程序呼叫核心来运行所需的工作。

查看有哪些Shell:

1
$ cat /etc/shells

查看使用的是哪个Shell:

1
$ cat /etc/passwd

Bash(Bourne-Again SHell)是一个命令处理器,通常运行于文本窗口中,并能执行用户直接输入的命令。/bin/bash 是 Linux 默认的 shell。

提示符:

  • $ 普通用户
  • # 超级用户(root)

命令一般由三个部分组成:命令 选项 参数

bash 主要的优点:

  1. 命令编修能力 (history)
  2. 命令与文件补全功能: (tab 按键的好处)
  3. 命令别名配置功能
  4. 工作控制、前景背景控制
  5. 程序化脚本
  6. 通配符

特殊符号:

  • * (代表零个或多个任意字符)
  • ? (只代表一个任意的字符)
  • # (注释符号)
  • \ (脱义字符)

历史记录

按『上下键』就可以找到前/后一个输入的命令,还可以查看文件 ∼/.bash_history

  • !! 重复前一个命令
  • !字符 重复前一个以‘字符’开头的命令
  • !num 按照历史记录的序号执行命令
  • !?abc 重复之前包含’abc’的命令
  • !-n 重复前第n个命令
  • 通过 Ctrl+R 在历史记录中搜索命令
  • 重新调用前一个命令中的参数: 按esc之后再按.

补全功能

Tab 接在一串命令的第一个字的后面,则为命令补全;

Tab 接在一串命令的第二个字以后时,则为『文件补齐』。

命令别名

1
alias 别名='命令'

将linux下的rm命令改造成移动文件至回收站:

步骤1:

1
2
mkdir -p ∼/.trash
vim ∼/.bashrc

步骤2:

1
2
3
4
alias rm='trash'
trash() {
mv $@ ∼/.trash/
}

工作控制、前景背景控制

  • 在后台运行进程:在命令后添加一个&

  • 暂停某个程序:Ctrl+Z

  • 管理后台作业:jobs、bg、fg

通配符

  • * 匹配0个或多个
  • ? 匹配任意一个字符
  • [list] 匹配list中任意一个字符
  • [!list][^list] 匹配除了list中任意一个字符
  • [c1-c2] 匹配区间内的任何一个字符
  • [!c1-c2][^c1-c2]匹配区间外的任何一个字符
  • {str1,str2,...} 匹配str1,str2,…其一

注意通配符中不能出现空格。

变量

  • 定义: 变量名=变量值
    注意: 等号前后不能存在空格
  • 使用:$变量名${变量名},或使用反单引号

双引号: 允许引用、转义

单引号: 禁止引用、转义

反撇号, 或者$(): 以命令输出进行替换

环境变量:用来记录、设置运行参数

  • HOME: 代表用户的家目录。
  • SHELL: 目前这个环境使用的 SHELL 是哪个程序. Linux 默认使用 /bin/bash
  • HISTSIZE: 命令历史记录数量。
  • MAIL: 使用 mail 这个命令在收信时,系统会去读取的邮件信箱文件。
  • PATH: 就是运行文件搜寻的路径,目录与目录中间以冒号(:)分隔,由于文件的搜寻是依序由 PATH 的变量内的目录来查询,所以,目录的顺序也是重要的。
  • PWD:当前工作目录。
  • LANG:系统语言以及字符编码。
  • LOGNAME:登录用户名。

基本上,在 Linux 默认的情况中,使用大写的字母来配置的变量一般为系统内定需要的变量.

  • env:观察环境变量与常见环境变量说明
  • set:观察所有变量 (含环境变量与自定义变量).
  • export:自定义变量转成环境变量

特殊变量:(由系统或脚本控制, 不能直接赋值)

  • $?: 前一条命令的状态值: 0为正常, 非0为异常.
  • $0: 脚本自身的程序名.
  • $1 - $9: 第1 - 第9个位置参数
  • $*: 命令行的所有位置参数内容
  • $#: 命令行的位置参数个数
  1. 若该变量需要在其他子程序运行,则需要以 export 来使变量变成环境变量.
  2. 通常大写字符为系统默认变量,自行配置变量可 以使用小写字符,方便判断
  3. 取消变量的方法为使用 unset

在 PATH 这个变量当中添加:/home/dmtsai/bin

1
$ PATH=$PATH:/home/dmtsai/bin

将name的内容多出“yes”

1
$ name="$name"yes

命令的运行顺序

  1. 以相对/绝对路径执行命令,例如/bin/ls./ls
  2. 由alias找到该命令来执行
  3. 由bash内置的(builtin)命令来执行
  4. 通过$PATH这个变量的顺序找到第一个命令来执行
  5. 都没有找到:提示“command not found”

管道与重定向

大多数 UNIX/Linux 系统命令从终端接受输入并将所产生的输出发送回到终端.

  • 标准输入: 键盘
  • 标准输出: 显示屏

两种标准输出:

  • 标准输出 (STDOUT), 文件描述符 1
  • 标准错误输出 (STDERR), 文件描述符 2

一种标准输入:

  • 标准输入 (STDIN), 文件描述符为 0

重定向

重定向:改变程序运行的输入源输出地点.

文件重定向:改变程序运行的输入源为某个文件, 输出地点为某个文件.

  • >:输出重定向 文件不存在则创建 存在会覆盖原有文件
  • >>:输出重定向 文件不存在则创建 存在在原有文件后追加
  • <:输入重定向

特殊文件 /dev/null:写入到它的内容都会被丢弃,将命令的输出重定向到它,会起到”禁止输出”的效果.

同时重定位标准输出和标准错误输出到统一文件:&>file

管道

管道是可以看作是一种特殊的重定向,将一个命令的输出重定向为另一个命令的输入。
格式: 命令1|命令2|...|命令n。操作符|右侧命令必须能够接受标准输入。管道不会使用标准错误输出。

Here Document 与 Here String

Here Document 以两个连续的小于号<<开始,紧跟着一个特殊的字符序列,该字符序列在文档结尾处再次出现。它可以保存文字里面的换行或是缩进等空白字符。

Here String 以3个连续的小于号<<<开始,紧跟着一个字符串。

在Unix shell里,Here Document 和 Here String 通常用于给命令提供输入内容。

1
2
3
4
5
6
$ tr a-z A-Z <<END_TEXT
> one two three
> uno dos tres
> END_TEXT
ONE TWO THREE
UNO DOS TRES

END_TEXT被用作标识符,它指定了Here Document的开始和结束。

<<后面添加一个减号,可以使TAB被忽略。这允许在shell脚本中缩进Here Document而不改变它们的值。

1
2
3
4
5
6
$ tr a-z A-Z <<-END_TEXT
> one two three
> uno dos tres
> END_TEXT
ONE TWO THREE
UNO DOS TRES

默认地,会进行变量替换和命令替换:

1
2
3
4
$ cat << EOF
> Working dir $PWD
> EOF
Working dir /home/user

这可以通过使用引号包裹标识符来禁用。可以使用单引号或双引号:

1
2
3
4
$ cat << "EOF"
> Working dir $PWD
> EOF
Working dir $PWD

bash,ksh或zsh中也可以用Here String:

1
2
$ tr a-z A-Z <<<"Yes it is a string"
YES IT IS A STRING

cut

将一行中的一段信息“切”出来, 处理的信息是以“行”为单位.

  • -d:自定义分隔符,默认为tab。
  • -c:以字符为单位进行分割,指定字符范围。
  • -f:指定显示哪个区域(字段)
1
2
3
4
5
6
# 将PATH变量取出,找出第5个路径.
$ echo $PATH | cut -d ':' -f 5
# 将PATH变量取出,找出第3, 第5个路径.
$ echo $PATH | cut -d ':' -f 3,5
#$PATH输出的信息取得第12字符以后的所有字符串
$ echo $PATH | cut -c 12-

sort

按行排序

选项与参数:

  • -f :忽略大小写的差异,例如 A 与 a 视为编码相同;
  • -b :忽略最前面的空格符部分;
  • -M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法;
  • -n :使用『纯数字』进行排序(默认是以文字型态来排序的);
  • -r :反向排序;
  • -u :就是 uniq ,相同的数据中,仅出现一行代表;
  • -t :分隔符,默认是用 [tab] 键来分隔;
  • -k :以那个区间 (field) 来进行排序的意思
1
2
3
4
5
6
7
8
# 个人账号都记录在 /etc/passwd 下,请将账号进行排序
$ cat /etc/passwd | sort -t ':' -k1
$ sort -t ':' -k1 /etc/passwd
$ sort -t ':' -k1 < /etc/passwd
# /etc/passwd 内容是以 : 来分隔的,我想以第三栏按数字大小来排序
$ cat /etc/passwd | sort -t ':' -k3 -n
# 利用 last ,将输出的数据仅取账号,并加以去重排序
$ last | cut -d ' ' -f1 | sort -u

uniq

uniq 可检查文本文件中(相邻)重复出现的行。

  • -c--count 在每列旁边显示该行重复出现的次数。
  • -d--repeated 仅显示重复出现的行。
  • -i忽略大小写。
  • -u--unique 仅显示出一次的行。

wc

计算输出的信息的整体数据(默认输出行数、字数、字符数)

  • -l:仅列出;
  • -w :仅列出多少(英文单字);
  • -m :多少字符;
1
2
# 以一行命令取得这个月份登陆系统的总人次
$ last | grep "[a-zA-Z]" | grep -v 'wtmp' | wc -l

tee

同时将数据流分送到文件去与屏幕 (stdout)

  • -a : 追加到文件

tr

用来删除一段信息当中的文字,或者是进行文字信息的替换

1
tr [OPTION] SET1 [SET2]

当SET1和SET2给出, 并且没有给出-d选项, 替换SET1中的每一个字符为相应SET2中的字符.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ
$ tr [:lower:] [:upper:]
$ tr a-z A-Z
$ tr ’ {} ’ ’()’ <inputfile> outputfile
$ echo "This is for testing" | tr [:space:] '.'
This.is.for.testing.
$ echo "This is for testing" | tr [:space:] '.'
This..is..for..testing.
$ echo "This is for testing" | tr -s [:space:] '.'
This.is.for.testing.
# 删除某个字符
$ echo "the geek stuff" | tr -d 't'
he geek suff
$ echo "my username is 432234" | tr -d [:digit:]
my username is
# 仅保留可打印字符
$ tr -cd [:print:] < file.txt

paste

paste 将两个文件中的每一行合并为一行, 中间以 [tab] 键隔开. paste [-d] file1 file2

选项与参数:

  • -d :后面可以接分隔字符。默认是以 [tab] 来分隔
  • -:如果 file 部分写成 - ,表示来自 standard input 的数据。

expand

将 [tab] 按键转成空格键

  • -t:后面接数字。默认一个 tab 按键用 8 个空格键取代。

split

将一个大文件依据文件大小或行数来分割. split [-bl] file PREFIX

  • -b :后面可接欲分割成的文件大小,可加单位,例如 b, k, m 等;
  • -l :以行数来进行分割。
  • PREFIX :代表前导符的意思,可作为分割后文件的前导文字。
1
2
$ split -b 300k /etc/termcap termcap 
$ ls -al / | split -l 10 - lsroot

Shell基础练习

1、设置环境变量 HISTSIZE , 使其能够保存10000条命令历史。

修改/etc/profile文件里抽HISTSIZE的值为10000

2、为什么如果这样设置PS1 (PS1="[\u@\h \W]\$ ") 显示的结果和我们预想的不一样,那要如何设置才能恢复原来默认的?

PS1='[\u@\h \W]\$'

3、想办法把当前目录下的文件的文件名中的小写字母全部替换为大写字母。

1
2
3
4
5
for f in `ls`; do 
if echo $f | grep -q '[a-z]'; then
mv $f `echo $f | tr [a-z] [A-Z]`;
fi;
done

4、使用sort以":"为分隔符,对/etc/passwd文件的第5段排序。

1
sort -k5 -t: /etc/passwd

5、使用cut以":"为分隔符,截出/etc/passwd的第三段字符。

1
cut -d : -f 3 /etc/passwd

6、简述这几个文件的作用:

系统环境变量 与 个人环境变量

  • /etc/profile: 预设了几个重要的变量,例如 PATH, USER, LOGNAME…

  • /etc/bashrc: 预设umask和PS1

  • ~/.bashrc: 专属于当前用户shell的bash信息

  • ~/.bash_profile: 当前用户的个性化路径与环境变量的文件

7、export 的作用是什么?

将局部变量转换为全局变量

局部变量: 仅在当前的shell下有效

全局变量: 在当前的shell以及其各子shell下均有效

8、linux下自定义变量要符合什么样的规则呢?

变量名: 以字母数字和下划线, 但不能以数字开头

变量定义的格式如: a=b, 其中a表示变量, b表示变量的内容,=号前后不能出现空格.

变量的引用: $a, 前在需要加$符号, 或者 ${a}

9、如何把要运行的命令丢到后台跑?又如何把后台跑的进程给调到前台?

把正在前台运行的命令放到后台:

(1) Ctrl + Z 暂停 (会打印作业编号); (2) bg 作业编号

把还未运行的作业放到后台:

在命令后面加个&, 如:sleep 60 &

后台命令放到前台

fg 作业编号

10、 列出当前目录下以"test"开头的文件和目录。

ls test*

11、 如何把一个命令的输出内容不仅打印到屏幕上而且还可以重定向到一个文件内?

使用tee 命令. 举例: ls ./ | tee abc.txt

12、假如有个命令很长,我们如何使用一个简单的字符串代替这个复杂的命令呢?请举例说明。

使用alias别名

alias abc="………………"

后面执行 abc

13、我如何实现这样的功能,把一条命令丢到后台运行,而且把其正确输出和错误输出同时重定向到一个文件内?

perl test.pl 1> a.txt 2>&1 &

14、如何按照大小(假如按照10M)分隔一个大文件,又如何按照行数(假如10000行)分隔?

使用split命令

split -b 10M abc.txt abc

split -l 10000 abc.txt abc

15、做实验,搞明白 ; && || 这三个符号的含义。

命令1 ; 命令2 : 命令1和命令2之间没有什么关系,两个命令都会执行,先执行命令1再执行命令2

命令1 && 命令2 : 如果命令1执行不成功, 那么不会执行命令2;如果命令1执行成功,执行命令2

是否执行成功,返回值是否为0 (0表示成功)

命令1 || 命令2 : 如果命令1执行成功, 则不执行命令2; 否则执行命令2

16、如果只想让某个用户使用某个变量如何做?

个人环境变量的配置

可以去修改该用户的 ~/.bashrc文件, 比如增加一行 abc=123

然后再执行source ~/.bashrc

17、使用哪个命令会把系统当中所有的变量以及当前用户定义的自定义变量列出来?

使用set命令

18、统计当前价目录中符号连接文件的个数:

1
$ ls -lah | grep "^l" | wc -l

正则表达式

正则表达式是与一组字符串匹配的模式。 模式由运算符,文字字符和元字符组成,它们具有特殊的含义。

^符号与一行开头的字符串匹配。 $符号与行的结尾字符串匹配。

匹配所有空行^$模式,即开头与结束都没有内容。

.符号是与任何单个字符(除换行符)匹配的元字符。

[]中括号表达式允许将字符括在中括号[]来匹配一组字符,可以在括号内指定一系列字符,如果方括号内的第一个字符是符号^,则它将匹配方括号中未括起来的任何字符。

预定义字符 字符类
[:alnum:] 字母数字字符 。
[:alpha:] 字母字符。
[:blank:] 空格和制表符。
[:digit:] 数字。
[:lower:] 小写字母。
[:upper:] 大写字母。
量词 描述
* 将前一项匹配零次或多次。
? 将前一项匹配零或一次。
+ 匹配前一项一次或多次。
{n} 精确匹配前一项n次
{n,} 至少匹配n次。
{,m} 最多匹配前一项m次。
{n,m} 匹配前一项从nm 次。
反斜杠表达式 说明
\b 匹配单词边界。
\< 匹配单词开头的空字符串。
\> 在单词末尾匹配一个空字符串。
\w 匹配一个单词。
\s 匹配空格。

grep

分析一行信息,如果当中有我们所需要的信息,就将该行拿出来.

grep [-acinv] [- -color=auto] '查找字符串' filename

选项与参数:

  • -a : 将 binary 文件以 text 文件的方式搜寻数据
  • -c : 计算找到 ’搜寻字符串’ 的次数
  • -i : 忽略大小写的不同,所以大小写视为相同
  • -n : 顺便输出行号
  • -v : 反向选择,亦即显示出没有 ’搜寻字符串’ 内容的那一行
  • -w : 匹配整词
  • –color=auto : 可以将找到的关键词部分加上颜色的显示
1
2
3
4
5
6
7
8
9
10
11
# 将 last 当中,有出现 root 的那一行就取出来; 
# last: 登录到这台机器的用户记录
$ last | grep 'root'
# 与上一条相反,只要没有 root 的就取出.
$ last | grep -v 'root'
# 在 last 的输出信息中,只要有 root 就取出,并且仅取第一栏.
$ last | grep 'root' | cut -d ' ' -f1
# 取出 /etc/man.config 内含 MANPATH 的那几行
$ grep 'MANPATH' /etc/man.config
$ cat /etc/man.config | grep 'MANPATH'
$ grep 'MANPATH' < /etc/man.config

GNU grep 支持三种正则表达式语法Basic,Extended和Perl-compatible。

当没有给出正则表达式类型时,grep 以Basic的形式调用,grep将搜索模式解释为Basic正则表达式。 要将模式解释为Extended的正则表达式,请使用-E(或--extended-regexp)选项。

在Basic正则表达式中,元字符?+{|() 被解释为文字字符。 为了在使用基本正则表达式时保持元字符的特殊含义,必须使用反斜杠\)对字符进行转义。(扩展不需要转义)

应始终将正则表达式括在单引号中,以避免shell解释和扩展元字符。

如果搜索包含空格的字符串,则需要将其用单引号或双引号引起来。

awk(编程语言/数据处理引擎)

awk '模式 [操作]' 文件1 文件2 ......

-F 选项:设置分割字段当字符。

每个awk程序都是 一个或多个 模式-操作 语句的序列。awk 的基本操作是一行一行的扫描输入,搜索匹配任意程序中模式的行。“匹配”的准确意义是视具体的模式而言,对于模式 $3>0 来说,意思是“条件为真”。 每个模式依次测试每个输入行。对于匹配到行的模式,其对应的动作(也许包含多步)得到执行,然后读取下一行并继续匹配,直到所有的输入读取完毕。

由于模式和动作两者任一都是可选的,所以需要使用大括号包围动作用以区分其他模式。

假设存在一个文件 emp.data,其中包含员工的姓名、薪资(美元/小时)以及小时数,一个员工一行数据,其内容如下:

1
2
3
4
5
6
Beth	4.00	0
Dan 3.75 0
kathy 4.00 10
Mark 5.00 20
Mary 5.50 22
Susie 4.25 18

打印工作时长超过0小时的员工姓名和工资(薪资乘以时间):

1
2
3
4
5
$ awk '$3>0 {print $1, $2*$3}'  emp.data
kathy 40
Mark 100
Mary 121
Susie 76.5

打印没有工作过的员工姓名:

1
2
3
$ awk '$3==0 {print $1}'  emp.data
Beth
Dan

也可以省略命令行中的输入文件,仅仅输入: awk 'program codes' 在这种情况下,awk 将会应用于你在终端接着输入的任意数据行,直到你输入一个文件结束信号(Unix系统上为control-d)。示例:

1
2
3
4
5
$ awk '$3>0 {print $1}'
Mary 20 1000 #输入该行回车
Mary # 计算机输出,匹配到了信息
Belly 30 3000 #继续输入改行
Belly #计算机输出

注意事项: 命令行中的程序是用单引号包围着的。这会防止shell解释程序中$ 这样的字符,也允许程序的长度超过一行。 当程序比较长的时候,可以将程序写入到一个文件,以下命令行: awk -f programfile optional list of input files

其中 -f 选项指示 awk 从指定文件中获取程序。可以使用任意文件名替换 programfile。

awk中仅仅只有两种类型 数值字符 构成的字符串。通常情况下,一个字段是一个不包含任何空格或制表符的连续字符序列。 当前输入的 行中的第一个字段被称作 $1,第二个是 $2,以此类推。 整个行的内容被定义为 $0。 每一行的字段数量可以不同。

输出

如果一个动作没有任何模式,这个动作针对所有输入的行进行操作,print 语句用来打印(输出)当前输入的行,{print} 等效于 {print $0}

也可以指定特定的表达式: {print $1,$3}。在 print 语句中被逗号分隔的表达式,在默认情况下他们将会用一个空格分割来输出。每一行print生成的内容都会以一个换行符作为结束。还可以在字段中间或者计算的值中间打印输出想要的内容: {print "total pay for", $1, "is", $2*$3}

1
2
3
4
5
6
7
$ awk '{print "total pay for", $1, "is", $2*$3}' emp.data
total pay for Beth is 0
total pay for Dan is 0
total pay for kathy is 40
total pay for Mark is 100
total pay for Mary is 121
total pay for Susie is 76.5

NF: AWK 会对当前输入的行有多少字段进行计数,并且将当前行的字段数量存储在一个内建的称为 NF 的变量中。因此 {print NF,$1,$NF} 会打印出 每一行的字段数量、第一个字段的值、最后一个字段的值

1
2
3
4
5
6
7
$ awk '{print NF,$1,$NF}' emp.data
3 Beth 0
3 Dan 0
3 kathy 10
3 Mark 20
3 Mary 22
3 Susie 18

NR:存储了当前已经读取了多少行的计数。可以使用 NR 和$0给emp.data的每一行加上行号: {print NR,$0}

1
2
3
4
5
6
7
$ awk '{print NR,$0}' emp.data
1 Beth 4.00 0
2 Dan 3.75 0
3 kathy 4.00 10
4 Mark 5.00 20
5 Mary 5.50 22
6 Susie 4.25 18

printf 语句printf(format, value1, value2, ..., valueN)

其中 format 是字符串,包含要逐字打印的文本,穿插在 format 之后的每个值该如何打印的规格。一个规格是一个 % 符,后面跟着一些字符,用来控制一个 value 的格式。因此,有过少个 value 要打印,在 format 中就要有多少个 % 规格。 打印每个员工的总薪酬:

1
2
3
4
5
6
7
$ awk '{printf("total pay for %s is $%.2f\n", $1, $2*$3)}'  emp.data
total pay for Beth is $0.00
total pay for Dan is $0.00
total pay for kathy is $40.00
total pay for Mark is $100.00
total pay for Mary is $121.00
total pay for Susie is $76.50

排序

以薪酬递增的方式输出每一行:将awk的输出通过管道传给 **sort **命令

1
2
3
4
5
6
7
$ awk '{printf("%6.2f %s\n", $2*$3, $0)}' emp.data | sort
0.00 Beth 4.00 0
0.00 Dan 3.75 0
100.00 Mark 5.00 20
121.00 Mary 5.50 22
40.00 kathy 4.00 10
76.50 Susie 4.25 18

若要按照第一列的数值大小排序,sort需要添加 -k1n 选项。

选择

通过对比选择

使用一个对比模式来选择每小时赚5美元或更多的员工记录,亦即第二个字段大于等于5的行: $2>=5

1
2
3
$ awk '$2>=5'  emp.data
Mark 5.00 20
Mary 5.50 22
通过计算选择
1
2
3
4
$ awk '$2*$3>50 {printf("$%.2f for %s\n", $2*$3, $1)}' emp.data
$100.00 for Mark
$121.00 for Mary
$76.50 for Susie
通过文本内容选择

除了数值测试,还可以选择包含特定单词或短语的输入行。这个程序会打印所有第一个字段为 Susie 的行:$1=="Susie"

操作符 == 用于测试相等性。 也可以使用正则表达式的模式查找包含任意任意字母组合,单词或短语的文本。如以下可以匹配到任意位置包含Susie的行: /Susie/

1
2
$ awk '/Susie/'  emp.data
Susie 4.25 18
模式组合

可以使用括号和逻辑操作符号与&&、或||,以及非! 对模式进行组合。 $2>=4||$3>=20 会打印第二个字段大于等于4或者第三个字段大于等于20的行:

1
2
3
4
5
6
$ awk '$2>=4||$3>=20'  emp.data
Beth 4.00 0
kathy 4.00 10
Mark 5.00 20
Mary 5.50 22
Susie 4.25 18

BEGIN 与 END

特殊模式 BEGIN 用于匹配第一个输入文件的第一行之前的位置。END 则用于匹配处理过的最后一个文件的最后一行的位置。

这个程序使用 BEGIN 来输出一个标题:

1
2
3
4
5
6
7
8
9
$ awk 'BEGIN {print "Name RATE HOURS"; print ""} {print}' emp.data
Name RATE HOURS

Beth 4.00 0
Dan 3.75 0
kathy 4.00 10
Mark 5.00 20
Mary 5.50 22
Susie 4.25 18

注意事项:

  • awk 可以在一行上放多个语句,使用分号;进行分隔。
  • 普通的 print是打印当前输入行,print "" 则会打印一个空行。

AWK 是按一行一行地读取输入的。

  • 1.首先执行 BEGIN
  • 2.从输入中读取一行
  • 3.在这次读取的这一行中执行 AWK 命令
  • 4.如果文件还没有读取完毕,则重复步骤2、3
  • 5.执行 END 块中的 awk 命令

使用 AWK 进行计算

计数
1
2
$ awk '$3 > 15 {emp = emp + 1} END {print emp, "employees worked more than 15 hours"}' emp.data
3 employees worked more than 15 hours

用作数字的 awk 变量的默认初始值为0, 所以不需要初始化 emp。创建一个变量emp初始值为0,如果读入的那一行的第三个字段大于15,则emp在自身值的基础上自增1,读完最后一行后输出存在多少个员工工作时长超过15个小时的语句。

求和与平均值

为计算员工数目,可以使用内置变量 NR,保存了当前位置读取的行数;在所有输入的结尾它的值就是所读行的总行数。

1
2
$ awk 'END {print NR, "employees"}'  emp.data
6 employees

如下是一个使用 NR 来计算薪酬均值的程序:

1
2
3
4
5
6
7
8
$ awk '{pay = pay + $2*$3} \
END {print NR, "employees" \
print "total pay is", pay \
print "average pay is", pay/NR \
}' emp.data
6 employees
total pay is 337.5
average pay is 56.25

注意awk程序中变量不需要以$开头!

处理文本

awk 的优势之一是能像大多数语言处理数字一样方便地处理字符串。 awk 可以保存数字也可以保存字符。找出时薪最高的员工:

1
2
3
4
5
$ awk '$2 > maxrate { maxrate = $2; maxemp = $1 } \
END { print "highest hourly rate:", maxrate, "for", maxemp }' emp.data
highest hourly rate: 5.50 for Mary
$ awk '{names = names $1 " "} END {print names}' emp.data
Beth Dan kathy Mark Mary Susie

打印最后一个输入行

虽然在 END 动作中 NR 还保留着它的值, 但 $0 没有。

1
2
$ awk ' {last = $0} END {print last}' emp.data
Susie 4.25 18

sed

sed全名叫stream editor,流编辑器,用程序的方式来编辑文本。

用s命令替换

使用下面的这段文本做演示:

1
2
3
4
5
6
7
8
9
$ cat pets.txt
This is my cat
my cat's name is betty
This is my dog
my dog's name is frank
This is my fish
my fish's name is george
This is my goat
my goat's name is adam

把其中的my字符串替换成Hao Chen’s(s表示替换命令,/my/表示匹配my,/Hao Chen’s/表示把匹配替换成Hao Chen’s,/g 表示一行上的替换所有的匹配):

1
2
3
4
5
6
7
8
9
$ sed "s/my/Hao Chen's/g" pets.txt
This is Hao Chen's cat
Hao Chen's cat's name is betty
This is Hao Chen's dog
Hao Chen's dog's name is frank
This is Hao Chen's fish
Hao Chen's fish's name is george
This is Hao Chen's goat
Hao Chen's goat's name is adam

注意:如果你要使用单引号,那么你没办法通过\’这样来转义,就有双引号就可以了,在双引号内可以用\”来转义。

再注意:上面的sed并没有对文件的内容改变,只是把处理过后的内容输出,如果要写回文件可以使用重定向,如:

1
$ sed "s/my/Hao Chen's/g" pets.txt > hao_pets.txt

或使用 -i 参数直接修改文件内容:

1
$ sed -i "s/my/Hao Chen's/g" pets.txt

在每一行最前面加点东西:

1
2
3
4
5
6
7
8
9
$ sed 's/^/#/g' pets.txt
#This is my cat
# my cat's name is betty
#This is my dog
# my dog's name is frank
#This is my fish
# my fish's name is george
#This is my goat
# my goat's name is adam

在每一行最后面加点东西:

1
2
3
4
5
6
7
8
9
$ sed 's/$/ --- /g' pets.txt
This is my cat ---
my cat's name is betty ---
This is my dog ---
my dog's name is frank ---
This is my fish ---
my fish's name is george ---
This is my goat ---
my goat's name is adam ---

要去掉某html中的tags:

1
<b>This</b> is what <span style="text-decoration: underline;">I</span> meant. Understand?

sed命令:

1
2
3
4
5
6
7
8
# 如果你这样搞的话,就会有问题
$ sed 's/<.*>//g' html.txt
Understand?

# 要解决上面的那个问题,就得像下面这样。
# 其中的'[^>]' 指定了除了>的字符重复0次或多次。
$ sed 's/<[^>]*>//g' html.txt
This is what I meant. Understand?

指定需要替换的内容:

1
2
3
4
5
6
7
8
9
$ sed "3s/my/your/g" pets.txt
This is my cat
my cat's name is betty
This is your dog
my dog's name is frank
This is my fish
my fish's name is george
This is my goat
my goat's name is adam

下面的命令只替换第3到第6行的文本。

1
2
3
4
5
6
7
8
9
$ sed "3,6s/my/your/g" pets.txt
This is my cat
my cat's name is betty
This is your dog
your dog's name is frank
This is your fish
your fish's name is george
This is my goat
my goat's name is adam
1
2
3
4
5
$ cat my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
This is my goat, my goat's name is adam

只替换每一行的第一个s:

1
2
3
4
5
$ sed 's/s/S/1' my.txt
ThiS is my cat, my cat's name is betty
ThiS is my dog, my dog's name is frank
ThiS is my fish, my fish's name is george
ThiS is my goat, my goat's name is adam

只替换每一行的第二个s:

1
2
3
4
5
$ sed 's/s/S/2' my.txt
This iS my cat, my cat's name is betty
This iS my dog, my dog's name is frank
This iS my fish, my fish's name is george
This iS my goat, my goat's name is adam

只替换每一行的第3个以后的s:

1
2
3
4
5
$ sed 's/s/S/3g' my.txt
This is my cat, my cat'S name iS betty
This is my dog, my dog'S name iS frank
This is my fiSh, my fiSh'S name iS george
This is my goat, my goat'S name iS adam

多个匹配

如果我们需要一次替换多个模式,可参看下面的示例:(第一个模式把第一行到第三行的my替换成your,第二个则把第3行以后的This替换成了That)

1
2
3
4
5
$ sed '1,3s/my/your/g; 3,$s/This/That/g' my.txt
This is your cat, your cat's name is betty
This is your dog, your dog's name is frank
That is your fish, your fish's name is george
That is my goat, my goat's name is adam

上面的命令等价于:(注:下面使用的是sed的-e命令行参数)

1
$ sed -e '1,3s/my/your/g' -e '3,$s/This/That/g' my.txt

我们可以使用**&**来当做被匹配的变量,然后可以在基本左右加点东西。如下所示:

1
2
3
4
5
$ sed 's/my/[&]/g' my.txt
This is [my] cat, [my] cat's name is betty
This is [my] dog, [my] dog's name is frank
This is [my] fish, [my] fish's name is george
This is [my] goat, [my] goat's name is adam

圆括号匹配

使用圆括号匹配的示例:(圆括号括起来的正则表达式所匹配的字符串会可以当成变量来使用,sed中使用的是\1,\2…)

1
2
3
4
5
$ sed 's/This is my \([^,&]*\),.*is \(.*\)/\1:\2/g' my.txt
cat:betty
dog:frank
fish:george
goat:adam

上面这个例子中的正则表达式有点复杂,解开如下(去掉转义字符):

正则为:This is my ([^,]*),.*is (.*)
匹配为:This is my (cat),......is (betty)

然后:\1就是cat\2就是betty

sed命令

N命令

先来看N命令 —— 把下一行的内容纳入当成缓冲区做匹配。

下面的的示例会把原文本中的偶数行纳入奇数行匹配,而s只匹配并替换一次,所以,就成了下面的结果:

1
2
3
4
5
6
7
8
9
$ sed 'N;s/my/your/' pets.txt
This is your cat
my cat's name is betty
This is your dog
my dog's name is frank
This is your fish
my fish's name is george
This is your goat
my goat's name is adam

也就是说,原来的文件成了:

1
2
3
4
This is my cat\n  my cat's name is betty
This is my dog\n my dog's name is frank
This is my fish\n my fish's name is george
This is my goat\n my goat's name is adam

这样一来,下面的例子你就明白了,

1
2
3
4
5
$ sed 'N;s/\n/,/' pets.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
This is my goat, my goat's name is adam
a命令和i命令

a命令就是append, i命令就是insert,它们是用来添加行的。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 其中的1 i表明,其要在第1行前插入一行(insert)
$ sed "1 i This is my monkey, my monkey's name is wukong" my.txt
This is my monkey, my monkey's name is wukong
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
This is my goat, my goat's name is adam

# 其中的1 a表明,其要在最后一行后追加一行(append)
$ sed "1 a This is my monkey, my monkey's name is wukong" my.txt
This is my cat, my cat's name is betty
This is my monkey, my monkey's name is wukong
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
This is my goat, my goat's name is adam

我们可以运用匹配来添加文本:

1
2
3
4
5
6
7
# 注意其中的/fish/a,这意思是匹配到/fish/后就追加一行
$ sed "/fish/a This is my monkey, my monkey's name is wukong" my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
This is my monkey, my monkey's name is wukong
This is my goat, my goat's name is adam

下面这个例子是对每个存在my的行都新插入一行----

1
2
3
4
5
6
7
8
9
$ sed "/my/a ----" my.txt
This is my cat, my cat's name is betty
----
This is my dog, my dog's name is frank
----
This is my fish, my fish's name is george
----
This is my goat, my goat's name is adam
----
c命令

c 命令是替换匹配行

1
2
3
4
5
6
7
8
9
10
$ sed "2 c This is my monkey, my monkey's name is wukong" my.txt
This is my cat, my cat's name is betty
This is my monkey, my monkey's name is wukong
This is my fish, my fish's name is george
This is my goat, my goat's name is adam
$ sed "/fish/c This is my monkey, my monkey's name is wukong" my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my monkey, my monkey's name is wukong
This is my goat, my goat's name is adam
d命令

删除匹配行

1
2
3
4
5
6
7
8
9
10
$ sed '/fish/d' my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my goat, my goat's name is adam
$ sed '2d' my.txt
This is my cat, my cat's name is betty
This is my fish, my fish's name is george
This is my goat, my goat's name is adam
$ sed '2,**$d**' my.txt
This is my cat, my cat's name is betty
p命令

打印命令,可以把这个命令当成grep式的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 匹配fish并输出,可以看到fish的那一行被打了两遍,
# 这是因为sed处理时会把处理的信息输出
$ sed '/fish/p' my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
This is my fish, my fish's name is george
This is my goat, my goat's name is adam

# 使用n参数就好了
$ sed -n '/fish/p' my.txt
This is my fish, my fish's name is george

# 从一个模式到另一个模式
$ sed -n '/dog/,/fish/p' my.txt
This is my dog, my dog's name is frank
This is my fish, my fish's name is george

#从第一行打印到匹配fish成功的那一行
$ sed -n '1,/fish/p' my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
命令打包

第二个是cmd可以是多个,它们可以用分号分开,可以用大括号括起来作为嵌套命令。下面是几个例子:

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
30
31
32
33
34
35
$ cat pets.txt
This is my cat
my cat's name is betty
This is my dog
my dog's name is frank
This is my fish
my fish's name is george
This is my goat
my goat's name is adam

# 对3行到第6行,执行命令/This/d
$ sed '3,6 {/This/d}' pets.txt
This is my cat
my cat's name is betty
my dog's name is frank
my fish's name is george
This is my goat
my goat's name is adam

# 对3行到第6行,匹配/This/成功后,再匹配/fish/,成功后执行d命令
$ sed '3,6 {/This/{/fish/d}}' pets.txt
This is my cat
my cat's name is betty
This is my dog
my dog's name is frank
my fish's name is george
This is my goat
my goat's name is adam

# 从第一行到最后一行,如果匹配到This,则删除之;如果前面有空格,则去除空格
$ sed '1,${/This/d;s/^ *//g}' pets.txt
my cat's name is betty
my dog's name is frank
my fish's name is george
my goat's name is adam

正则表达式练习

1、如何把 /etc/passwd 中用户uid 大于500 的行给打印出来?

1
awk -F ':' '$3>500 {print}' /etc/passwd

2、awk中 NR,NF两个变量表示什么含义?awk -F ':' '{print $NR}' /etc/passwd 会打印出什么结果出来?

NR表示行数,NF表示一共有多少段。会打印出/etc/passwd文件中每行中和行数相同的列; 如果不存在,会输出空串.

3、用grep把1.txt文档中包含’abc’或者’123’的行过滤出来,并在过滤出来的行前面加上行号.

1
grep -E -n '123|abc' 1.txt

4、grep -v '^$' 1.txt 这样会过滤出哪些行?

过滤出不为空行的行(非空白行)

5、'.' '*''.*' 分别表示什么含义?'+''?'表示什么含义,这五个符号是否可以在grep中使用,是否可以在grep、sed以及awk中使用?

'.':匹配任意单个字符

'*':将前一项匹配零次或多次

'.*':匹配任意零至多个字符

'+':将前一项匹配一次或多次

'?':将前一项匹配零次或一次

都可以在grep中使用,其中?+需要使用反斜杠转义或使用-E选项。

都可以在awk中使用,需使用//包裹。

6、grep 里面用到一个 {} ,它在什么情况下使用?

用法 情况
{n} 精确匹配前一项n次
{n,} 至少匹配n次。
{,m} 最多匹配前一项m次。
{n,m} 匹配前一项从n至m 次。

7、截取日志1.log的第一段(以空格为分隔符), 按数字排序、然后去重,但是需要保留重复的数量如何做?

1
awk '{print $1}' 1.log | sort -n | uniq -c

8、使用awk过滤出1.log中第7段(空格分隔)为’200’ 并且第8段为’11897’的行。

1
awk '$7==200&&$8==11897 {print}' 1.log

9、请比较这两个命令的异同:grep -v '^[0-9]' 1.txtgrep '^[^0-9]' 1.txt

grep -v '^[0-9]' 1.txt会过滤出1.txt中除了以0-9开头的行以外的所有行;

grep '^[^0-9]' 1.txt会过滤出1.txt中不以0-9开头的行。

两者输出结果中都不包含以0-9开头的行,但前者输出中包含空行,后者不包含。

10、awk中的$0表示什么?为什么以下两条命令的$0结果不一致呢? awk -F ':' '{print $0}' 1.txtawk -F ':' '$7=1 {print $0}' 1.txt

$0 表示整个行的内容

awk -F ':' '{print $0}' 1.txt 会原样输出每一行

awk -F ':' '$7=1 {print $0}' 1.txt 会将第7列替换为1,输出时不会输出分割字符串

11、使用grep过滤某个关键词时,如何把包含关键词的行连同上面一行打印出来,那下面一行呢?同时上面和下面都打印出来呢?

连同上面一行:-A1

下面一行:-B1

同时上面和下面都打印出来:-C1

12、假设stu.data的内容为如下所示,

1
2
3
4
5
Name Course1 Course2 Course3
Mary 89 87 76
Tom 98 78 89
Tony 89 78 87
Adam 90 76 87

请使用awk编程,打印出平均分最高的学生姓名及其平均分.

1
2
3
4
5
6
7
8
9
$ awk 'BEGIN { max_score=0; name=""; }
{ if (NR >= 2) {
score=($2+$3+$4)/3;
if (score > max_score) {
max_score=score;
name=$1;
}
} }
END {print name,max_score}' stu.data
1
2
3
$ awk ' BEGIN { score=0 }
{ if (NR > 1 && ($2+$3+$4) > score) { score=$2+$3+$4; name=$1 } }
END {print name,score/3}' stu.data

13、sed有一个选项,可以直接更改文本文件,是哪个选项?

-i 选项

14、sed -i 's/.*ie//;s/["|&].*//' file 这条命令表示什么操作呢?

删除文档中以ie结尾的字符串和以"|&开头的字符串

15、如何删除一个文档中的所有数字或者字母?

1
sed -i 's/[a-zA-Z0-9]//g' filename

16、实现两个集合的交并差。假设两个文本文件a.txt和b.txt作为集合,每一行可以看作一个集合元素(每个文本中每行不重复)。

  • A交B:cat a.txt b.txt | sort | uniq -d

    -d 表示的是输出出现次数大于1的内容

  • A并B:cat a.txt b.txt | sort | uniq

  • A-B:cat a.txt b.txt b.txt | sort | uniq -u

    -u 表示的是输出出现次数为1的内容