Shell基础

history - 历史命令

history命令可以展示我们执行过的命令。执行过的命令,在正常退出终端的时候,都会报错在/root/.bash_history文件中。在退出终端前,执行过的命令在内存里面。

环境变量$HISTSIZE定义了.bash_history可以保存多少条历史命令。在/etc/profile文件里可以修改$HISTSIZE的值:

$ vim /etc/profile
HISTSIZE=1000           # 1000是默认值,修改数字即可
HISTTIMEFORMAT="%Y/%m/%d %H:%M:%S "     # 添加此条可显示命令执行时间

$ source /etc/profile   # 执行生效

$ chattr +a ~/.bash_history  # 防止被意外删除

终端操作:

  • history -c:清除当前历史命令

  • !!:执行上一条命令

  • !n:执行.bash_history文件中的第n条命令

  • !word:执行.bash_history文件中最近一次以word开头的命令


命令补全及别名

命令补全:终端输入命令或文件时,按tab键一下可以补全。按tab键两下可以列出当前关键字开头的所有命令或文件。

Centos7支持命令参数自动补全。需要安装bash-completion包:

$ yum install -y bash-completion

别名:

# 查看当前所有的别名
$ alias

# 新建别名。别名保存在/etc/profile.d/目录下和当前用户的.bashrc文件中。用户自定义的别名都保存在.bashrc文件中
$ alias lf='ls -a'      # 临时别名
$ vim ~/.bashrc         # 永久别名
alias lf='ls -a'

# 取消自定义别名
$ unalias lf

通配符

  • *:匹配任意字符

  • ?:匹配任意单个字符

  • []:匹配任意个括号中的字符或数字,可以在中括号加入0-9 a-z A-Z,例如:ls [ao]*表示以a和o开头的所有文件;ls [1-3].txt表示1.txt 2.txt 3.txt。

  • [!]:不匹配中括号中的字符或数字,例如:ls [!0-9]*表示不以数字开头的文件

  • {}:匹配花括号中的任意个或范围的字符或数字,例如:

$ ls {a,b,c}.txt        # 匹配a.txt b.txt c.txt
$ ls {lwz,zc}.txt       # 匹配lwz.txt zc.txt
$ touch {a..f}.txt      # 创建a到f这个范围的.txt文件

特殊符号

  • #:在终端输入命令时,命令行提示符#后面的符号是当前用户是root用户,例如:# ls;在文件中,#代表注释。

  • \:转义符;将特殊符号,转义成普通符号。

  • $:在终端输入命令时,命令行提示符$后面的符号就是当前用户,例如:$ ls;也是shell中变量的前缀。

  • !$:在正则表达式中表示行尾;在终端输入命令时,表示把上一条命令的参数作为标准输出,例如:

    •   $ ls aa.txt
        aa.txt
        $ !ls
        ls aa.txt
        aa.txt
        或者
        $ ls !$
        ls aa.txt
        aa.txt
      
  • ~:在终端时表示用户的家目录;在正则表达式中表示匹配符。

输入、输出、重定向

  • 0:标准输入,从键盘输入

  • 1:标准输出,输出到终端

  • 2:标准错误,输出到终端


  • >:输出重定向,将命令执行的结果输出到文件中,如果文件存在,则覆盖,如果不存在,则创建。

  • >>:输出重定向,将命令执行的结果追加到文件中,如果文件存在,则追加,如果不存在,则创建。

  • 2>:标准错误,将命令执行过程中的错误信息覆盖到文件中,正确结果不输入。

  • 2>>:标准错误,将命令执行过程中的错误信息追加到文件中,正确结果不输入。

  • &>:标准输出和标准错误都覆盖到文件中。

  • &>>:标准输出和标准错误都追加到文件中。

  • <:输入重定向,将文件中的内容作为命令的输入。

管道和任务控制

示例:cat 11.txt | wc -l;cat 11.txt|grep 'pass'

  • wc -l:统计文件内容的行数或者文件的数量。


管道:
  • |:管道,将一个命令的输出作为另一个命令的输入。

  • ;:任务分隔,多个任务依次执行。

  • &&:逻辑与,当第一个命令执行成功后,才执行第二个命令。

  • ||:逻辑或,当第一个命令执行失败后,才执行第二个命令。


任务控制:
  • &:后台运行,将任务放到后台执行。在命令结尾加上&符号,如:ping 127.0.0.1 &,后台运行ping命令。

  • ctrl + z:将任务放到后台,并暂停执行。(后台可以暂停多个任务)

  • jobs:查看当前后台任务。

  • fg [id]:将任务放到前台,并继续执行。

  • bg [id]:将暂停的任务放到后台,并继续执行。

  • sleep:延迟执行任务。默认以秒(s)为单位

    • 示例:sleep 10s;ls # 10秒后执行ls命令

    • s为秒,m为分,h为小时,d为天

  • kill [id]:结束任务,id为任务编号。


Shell变量

变量类型

  • 局部变量:局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。

  • 环境变量:所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。

  • shell变量:shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行。

环境变量配置文件

  • /etc/profile:系统级,定义系统全局环境变量,该文件中定义的环境变量会在用户登陆时被加载。

  • /etc/bashrc:系统级,但它仅对bash shell生效。当用户启动一个新交互式bash shell时,该文件会被读取,并设置bash shell特定的环境变量和执行一些系统级的初始化操作。

  • ~/.bashrc:用户级,用户的个人配置文件,该文件中的环境变量会在用户启动新的终端窗口时被加载。

  • ~/.bash_profile:用户级,该文件是每个用户的个人配置文件,该文件中定义的环境变量仅在该用户登陆时有效。

定义变量

name="lwz"      # 定义一个变量,变量名为name,变量值为lwz

注:变量名和等号之间不能有空格。同时,变量名的命名须遵循如下规则:

  • 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。

  • 中间不能有空格,可以使用下划线_

  • 不能使用标点符号。

  • 不能使用bash里的关键字(可用help命令查看保留关键字)。

用语句给变量赋值:

# 将/etc目录下的文件
for file in `ls /etc`
或者
for file in $(ls /etc)

单引号

$ myname='my name is lwz'

单引号字符串的限制:

  • 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的。

  • 单引号字符串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可以成对出现,作为字符串拼接使用。

双引号

$ name="lwz"
$ myname="my name is \"$name\" !"   # \为转义符,双引号中的双引号作用为拼接,转义后为普通双引号输出。
$ echo $myname
my name is "lwz" !
  • 双引号里可以用变量。

  • 双引号里可以出现转义字符。

反引号

反引号``,反引号中的内容会当做一个命令来执行,并且将执行结果作为变量的值。

# 将当前时间作为变量的值赋值给a。
$ a=`date`
$ echo $a
Wed Feb 26 16:37:15 CST 2019

拼接字符串

$ name="lwz"

# 双引号拼接
$ myname="myname: my name is "$name" !"
$ myname1="myname1: my name is ${name} !"
$ echo $myname $myname1
myname: my name is lwz ! myname1: my name is lwz !

# 单引号拼接
$ myname2='myname2: my name is '$name' !'
$ myname3='myname3: my name is ${name} !'
$ echo $myname2 $myname3
myname2: my name is lwz ! myname3: my name is ${name} !

使用变量

变量名前面加上美元符号$即可。

$ echo $name
或者
$ echo ${name}

变量名外的花括号{}是可选的,建议加上,避免歧义,类似如下情况。

$ echo ${name}123
lwz123

只读变量

使用readonly命令将变量定义为只读变量,只读变量的值不能被更改。

# 将变量"name"设置成只读变量
$ readonly name
$ name="qqq"
-bash: name: readonly variable

注意:设置为只读变量,有可能无法删除。

删除变量

# 使用unset命令删除变量
$ unset name

cut - 按列分割文本

cut命令将文本内容根据指定的规则分割成多列输出,默认分隔符为“TAB”。

语法

cut [选项参数] 文件

选项与参数:

  • -b:以字节为单位进行分割。

  • -n:与-b一起使用,表示禁止将字节分隔开来操作。

  • -c:以字符为单位进行分割。

  • -d:自定义分割字符。如果不加-d会默认字段分隔符为“TAB”;因此只能与-f一起使用。

  • -f:以字段为单位进行分割;根据-d的分隔字符将一段信息分割成为数段,再用-f取出第几段的意思(列号,提取第几列)。

  • -s:避免打印不包含分隔符的行。

  • --complement:补足被选择的字节、字符或字段(反向选择的意思,或者说是补集)。

  • --output-delimiter:更改输出分隔符;默认为输入分隔符。

示例

创建一个文本用于测试

[root@lwz ~]# cat rr.sh 
NO Name SubjectID Mark 备注
1  longshuai 001  56 不及格
2  gaoxiaofang  001 60 及格
3  zhangsan 001 50 不及格
4  lisi    001   80 及格
5  wangwu   001   90 及格
djakldj;lajd;sla

按字段进行分隔

rr.sh文本一共有5列,筛选出第二列和第四列,使用空格作为分隔符。

[root@lwz ~]# cut -d" " -f2,4 rr.sh 
Name Mark
 001
 
 001
 
 
djakldj;lajd;sla

因为文本内容不规则,所以输出的内容是非预期的。

使用tr工具将重复的多个空格视为单个:

[root@lwz ~]# cat rr.sh | tr -s " " | cut -d" " -f2,4 
Name Mark
longshuai 56
gaoxiaofang 60
zhangsan 50
lisi 80
wangwu 90
djakldj;lajd;sla

最后一行完全没有分隔符的行也输出了, 使用-s来过滤这样的输出:

[root@lwz ~]# cat rr.sh | tr -s " " | cut -d" " -f2,4 -s
Name Mark
longshuai 56
gaoxiaofang 60
zhangsan 50
lisi 80
wangwu 90

使用--complement输出除了第二列和第四列以外的所有列:

[root@lwz ~]# cat rr.sh | tr -s " " | cut -d" " -f2,4 -s --complement
NO SubjectID 备注
1 001 不及格
2 001 及格
3 001 不及格
4 001 及格
5 001 及格

按字节或字符分隔

英文和阿拉伯数字是单字节字符,中文是双字节或者是三字节。
使用-b来筛选字节,-c筛选字符。

注意:按字节或字符分隔时不能指定-d,因为-d是分隔字段的。

筛选第1个到第3个字节的内容:

[root@lwz ~]# cut -b1-3 rr.sh 
NO 
1 l
2 g
3 z
4 l
5 w
dja

筛选第3个到第6个字符的内容:

[root@lwz ~]# echo "今晚去哪喝!"|cut -c3-6
去哪喝!

--output-delimiter自定义分隔符

可以在多段分隔需要拼接时使用--output-delimiter来指定分隔符:

[root@lwz ~]# cut -b3-5,6-8 rr.sh   # 没有指定分隔符的拼接
 Name 
longsh
gaoxia
zhangs
lisi  
wangwu
akldj;

[root@lwz ~]# cut -b3-5,6-8 rr.sh --output-delimiter ":"    # 指定":"分隔拼接内容
 Na:me 
lon:gsh
gao:xia
zha:ngs
lis:i  
wan:gwu
akl:dj;

cut中的范围筛选

输出第3个字段往后的所有内容:

[root@lwz ~]# cut -d" " -f3- rr.sh -s
SubjectID Mark 备注
001  56 不及格
 001 60 及格
001 50 不及格
   001   80 及格
  001   90 及格

如果筛选的多段内容中有重复的,不会重复输出:

[root@lwz ~]# cut -d" " -f3-5,4-6 rr.sh -s      # 同`-f3-6`输出的内容一样。
SubjectID Mark 备注
001  56 不及格
 001 60 及格
001 50 不及格
   001
  001

在终端输入的分段顺序不会影响输出的顺序,cut只会按照文本内容的顺序输出:

[root@lwz ~]# cut -d" " -f4-6,2 rr.sh -s    # 两条命令输入的字段顺序不一样,但是输出内容还是一样的。
Name Mark 备注
longshuai  56 不及格
gaoxiaofang 001 60 及格
zhangsan 50 不及格
lisi   001
wangwu  001 

[root@lwz ~]# cut -d" " -f2,4-6 rr.sh -s
Name Mark 备注
longshuai  56 不及格
gaoxiaofang 001 60 及格
zhangsan 50 不及格
lisi   001
wangwu  001

sort - 内容排序

sort命令将文件内容的每一行作为比较对象,通过将不同行进行互相比较,按照指定的排序规则进行排序输出,默认正序输出。

语法

sort [选项] 文件

常用选项:

  • -u:不输出重复行。

  • -r:倒叙输出。

  • -n:按照数字排序。(特殊符号为0,所以输出在数字前面)

  • -o:将排序后的结果输出到指定文件。

  • -t:指定分隔符。(通常和-k一起使用)

  • -k:指定排序的列。(通常和-t一起使用)

其他选项:

  • -f:将小写字母转换为大写字母进行比较,即忽略大小写。

  • -b:忽略每行前面开始处的空格字符。

  • -m:合并已排序文件,而不是输出。

  • -M:将每一行的月份用月份全称代替,例如JAN小于FEB,等等。

  • -c:检查文件是否已经按照顺序排序,如果乱序,则输出第一个乱序的行的相关信息,最后返回1。

  • -C:与-c选项类似,如果乱序,不输出内容,仅返回1。

示例

# 不加参数输出正序
[root@lwz ~]# sort seq.txt
!!@#
../
123
apple
apple:10:2.5
banana
banana:30:5.5
orange
orange:20:3.4
pear
pear
pear:90:2.3

# -r倒叙
[root@lwz ~]# sort -r seq.txt
pear:90:2.3
pear
pear
orange:20:3.4
orange
banana:30:5.5
banana
apple:10:2.5
apple
123
../
!!@#

# -u去重,-r倒叙,并将输出-o写入文件
[root@lwz ~]# sort -ur seq.txt -o seq2.txt
[root@lwz ~]# cat seq2.txt
pear:90:2.3
pear
orange:20:3.4
orange
banana:30:5.5
banana
apple:10:2.5
apple
123
../
!!@#

# -t指定分隔符为 : ,-k指定以:分隔后的第2列排序
[root@lwz ~]# sort -t: -k2 seq.txt
!!@#
../
123
apple
banana
orange
pear
pear
apple:10:2.5
orange:20:3.4
banana:30:5.5
pear:90:2.3

wc - 内容统计

wc命令用于统计指定文件中的字节数、字数、行数,并将统计结果显示输出。

语法

wc [选项] [文件]

如果不加选项,默认统计文件中的总行数、总字数、总字节数,如果命令行中有文件名,则后面加上文件名依次输出,没有文件名则不输出文件名。
如果不加文件名,会从标准输入读取内容(一般是结合其他命令一起使用)。

选项:

  • -c:统计字节数。

  • -l:统计行数。

  • -m:统计字符数。

  • -w:统计字数。

示例

# 统计文件中的行数、字数、字节数,依次输出。
[root@lwz ~]# wc seq.txt
12 12 98 seq.txt

# 统计多个文件
[root@lwz ~]# wc -clw seq.txt seq2.txt      # 输出结果与不加选项相同
 12  12  98 seq.txt
 12  12  98 seq2.txt
 24  24 196 总用量

# -l统计行数(比较常用)
[root@lwz ~]# wc -l seq.txt
12 seq.txt

# 结合其他命令使用,统计ps -ef中的进程数
[root@lwz ~]# ps -ef | wc -l
92

uniq - 内容去重

uniq命令用于输出或忽略文件中重复的行,一般与sort命令结合使用。

语法

uniq [选项] [文件]

选项:

  • -c:在每列旁边显示该行重复出现的次数。

  • -d:仅显示重复出现的行列,只显示一行。

  • -D:显示所有重复的行,重复多少行就显示多少行。

  • -u:仅显示出一次的行,不显示重复的行。

  • -i:忽略大小写字符的不同。

  • -f [N]:忽略第N列。

  • -s [N]:忽略前面N个字符。

  • -w [N]:忽略第N个字符后的内容。

示例

# 示例文本uniq.txt
[root@lwz ~]# cat uniq.txt
My name is Delav
My name is Delav
MY name is Delav
I'm learning Java
I'm learning Java
I'm learning Java
who am i
WHO am i
Python is so simple
My name is Delav
That's good
That's good
And studying Golang

# 不加选项默认输出去重后的内容
[root@lwz ~]# uniq uniq.txt
My name is Delav
MY name is Delav
I'm learning Java
who am i
WHO am i
Python is so simple
My name is Delav
That's good
And studying Golang

# 只显示重复的行-d,并显示重复次数-c
[root@lwz ~]# uniq -dc uniq.txt
      2 My name is Delav
      3 I'm learning Java
      2 That's good

# 忽略第2列(注意My和MY,忽略后就算重复的)
[root@lwz ~]# uniq -f2 uniq.txt
My name is Delav
I'm learning Java
who am i
Python is so simple
My name is Delav
That's good
And studying Golang

# 忽略前面3个字符(注意who和WHO会算作重复的,从第4个字符后就不算重复了)
[root@lwz ~]# uniq -s3 uniq.txt
My name is Delav
I'm learning Java
who am i
Python is so simple
My name is Delav
That's good
And studying Golang

# 结合sort命令一起使用(uniq只会把相邻的行算作重复,使用sort就能把不相邻的重复行排序到一起了)
[root@lwz ~]# sort uniq.txt | uniq -c
      1 And studying Golang
      3 I'm learning Java
      3 My name is Delav
      1 MY name is Delav
      1 Python is so simple
      2 That's good
      1 who am i
      1 WHO am i

tee - 重定向到标准输出和文件

  • tee命令将一份标准输入分别重定向到标准输出/dev/stdout和指定的文件中,文件可以有多个。

  • tee命令与>>>的区别是,tee命令会将输出重定向到文件的同时也输出在终端上。

  • 标准错误不会被tee读取到。

例:

[root@centos03 ~]# who | tee -a who.out
root     pts/3        2023-10-26 10:20 (192.168.1.110)

[root@centos03 ~]# cat who.out
root     pts/3        2023-10-26 10:20 (192.168.1.110)
  • tee tee.txt:覆盖到文件,与>同理。

  • tee -a tee.txt:追加到文件,与>>同理。


tr - 替换或删除字符

语法:tr [参数] "被替换" "替换后"

参数:

  • -d:删除字符,即删除字符串中指定的字符。

  • -s:把连续重复的字符以单独一个字符表示。

  • -c:反选,即除了被指定的字符外其它的字符都处理。

  • -t:删除所有属于第一字符集且不属于第二字符集的字符。

示例:

# 将结果小写转换为大写
[root@lwz ~]# who | tr "a-z" "A-Z" | tee test.txt
ROOT     TTY1         2023-10-24 16:02
ROOT     PTS/0        2023-10-24 16:07 (192.168.1.110)

# 将结果中的数字依次替换成字母,0=a、1=b、2=c,以此类推
[root@lwz ~]# echo '01222345' | tr '0-9' 'abcdefghijk' | tee test.txt
abcccdef

# 删除结果中数字以外的内容
[root@lwz ~]# echo '542asd99@#$' | tr -dc '0-9' | tee test.txt
54299

# 删除echo结果中数字以外的内容,并且去除结果中的重复数字
[root@lwz ~]# echo '542asd999999@#$' | tr -dc '0-9' | tr -s '0-9' | tee test.txt
5429

替换的范围表示:

[:digit:]       # 所有数字
 
[:lower:]       # 所有小写字符
 
[:upper:]       # 所有大写字符
 
[:graph:]       # 所有可打印字符,不包括空格
 
 
[:print:]       # 所有可打印字符,包括空格
 
[:punct:]       # 所有的标点字符
 
[:space:]       # 所有横向或者纵向的空白
 
[:xdigit:]      # 所有十六进制数字,包括0-9,a-f,A-F

split - 分割字符串

split命令用于将一个文件分隔成数个。
该指定将大文件分割成较小的文件,在默认情况下将按照每1000行分割为一个小文件。

语法

split [选项] [被切割文件] [切割后的文件名]

选项:

  • -<行数>:指定按多少行切割。

  • -b<字节>:指定按多少字节切割。

  • -l<行数>:也是指定按多少行切割。

  • -C<>:与-b相似,但切割时会维持每行的完整性。

  • --version:显示版本信息。

  • --help:在线帮助。

示例:
按每50行分割为一个文件:

[root@lwz 111]# split -50 11.txt    # 当不指定分割后的文件名时,文件名默认以 xa 开头

[root@lwz 111]# ls
11.txt  xaa  xab  xac  xad  xae  xaf  xag  xah  xai

按每1kb大小分割为一个文件,并且分割后的文件名以a1开头:

[root@lwz 111]# split -b1k 11.txt a1

[root@lwz 111]# ls
11.txt  a1aa  a1ab  a1ac  a1ad  a1ae  a1af  a1ag  a1ah  a1ai  a1aj  a1ak  a1al  a1am

将上一条命令执行的结果,按每5行存储到文件:

[root@lwz 111]# ps -ef | split -5

[root@lwz 111]# ls
11.txt  xab  xad  xaf  xah  xaj  xal  xan  xap  xar
xaa     xac  xae  xag  xai  xak  xam  xao  xaq  xas