# 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 ```