bash的基础特性
目录
一、命令历史history
1.环境变量
HISTSIZE:命令历史记录的条数
HISTFILE:~/.bash_history定义了路径
HISTFILESIZE:命令历史文件记录历史的条数
history -d OFFSET
-c
-a:手动追加当前会话缓冲区的命令历史至历史文件中
history #:显示历史中最近的#条命令
2.调用历史中的命令
!#:重复执行第#条指令;
!!:重复执行上一条命令
!string:执行最近以string开头的命令
3.调用上一条命令的最后一个参数
!$:
ESC, .
Alt+.
4.控制命令历史的记录方式
环境变量:HISTCONTROL
ignoredups:忽略重复的命令;连续且相同方为“重复”
ignorespace:忽略所有以空白开头的命令
ignoreboth:ignoredups, ignorespace
export HISTCONTROL='ignorespace'
修改环境变量值的方式:export 变量名="值"
变量赋值:把赋值符号后面的数据存储于变量名指向的内存空间
问题:使用 export 赋值与直接使用 = 赋值
使用 export 赋值的变量能够被子进程继承,而直接使用 = 的不能;但是在子进程中,无论使用 export 还是 = 都无法改变父进程的值
二、命令补全
在内部命令和外部命令可能路径下查找已经输入字符串开头的命令
bash执行命令:
内部命令:
外部命令:①bash根据PATH环境变量定义的路径,自左而右在每个路径搜寻以给定命令名命名的文件,第一次找到即为要执行的命令,因此能够自动补全命令
②还可以hash中找
直接补全:[Tab] 用户给定的字符串只有一条惟一对应的命令
以用户给定的字符串为开头对应的命令不惟一,则再次Tab会给出列表
三、路径补全
把用户给出的字符串当做路径开头,并在其指定上级目录下搜索以指定的字符串开头的文件名
如果惟一:则直接补全
否则:再次Tab,给出列表
四、命令行展开
~:展开为用户的主目录
~USERNAME:展开为指定用户的主目录
{}:可承载一个以逗号分隔的列表,并将其展开为多个路径
/tmp/{a,b} = /tmp/a, /tmp/b
/tmp/{tom,jerry}/hi = /tmp/tom/hi, /tmp/jerry/hi
五、命令的执行结果状态
程序的执行状态结果(成功或失败)
bash使用特殊变量$?保存最近一条命令的执行状态结果:
0:成功
1-255:失败
用echo $?显示执行状态结果
程序执行有两类结果:
①程序的返回值
②程序的执行状态结果(成功或失败)
六、命令别名(alias)
1.通过alias命令实现
① alias
显示当前shell进程所有可用的命令别名
② alias NAME='VALUE'
定义别名NAME,其相当于执行命令VALUE
注意:在命令行中定义的别名,仅对当前shell进程有效;如果想永久有效,要定义在配置文件中
仅对当前用户:~/.bashrc
对所有用户有效:/etc/bashrc
问题:编辑方式给出的新配置不会立即生效,如何操作可以立即生效
命令:bash进程重新读取配置文件source /path/to/config_file
或 . /path/to/config_file
2.撤消别名:unalias
unalias [-a] name [name ...]
Note:如果别名同原命令的名称,则如果要执行原命令,可使用"\COMMAND"
七、glob (globbing)
bash中用于实现文件名“通配”
1.通配符:*, ?, []
① *
任意长度的任意字符
a*b:
aab, ab, a123b,
abc
② ?
任意单个字符
a?b:
aab
ab, a12b, abc
③ []
匹配指定范围内的任意单个字符
[0-9]
[a-z]:不区分字符大小写
④ [^]
匹配指定范围外的任意单个字符
[^0-9]
专用字符集合:
[:digit:]:任意数字,相当于0-9
P.S.[[:digit:]]相当于[0-9]
[:lower:]:任意小写字母
[:upper:]:任意大写字母
[:alpha:]:任意大小写字母
[:alnum:]:任意数字或字母
[:space:]:
[:punct:]:标点符号
[[email protected] me]$ ls a[[:lower:]]c
aac abc
转义:\
2.练习
①显示/tmp目录下所有以l开头,以一个小写字母结尾,且中间出现至少一位数字的文件或目录
# ls -d /tmp/l*[0-9]*[[:lower:]]
②显示/tmp目录下,以任意一位数字开头,且以非数字结尾的文件或目录
# ls -d /tmp/[0-9]*[^0-9]
③显示/tmp/目录下,以非字母开头,后面跟了一个字母及其它任意长度任意字符的文件或目录
# ls -d /tmp/[^[:alpha:]][[:alpha:]]*
④复制/etc目录下,所有以m开头,以非数字结尾的文件或目录至/tmp/tudou目录中
# cp -a /etc/m*[^0-9] /tmp/tudou
或# cp -r /etc/m*[^0-9] /tmp/tudou
⑤复制/etc目录下,所有以.d结尾的文件或目录至/tmp/tudou目录中
# cp -a /etc/*.d /tmp/tudou
或# cp -r /etc/*.d /tmp/tudou
⑥复制/etc目录下,所以有.conf结尾,且以m,n,r,p开头的文件或目录至/tmp/mageedu.com目录中
# cp -a /etc/[mnrp]*.conf /tmp/mageedu.com
八、bash的快捷键
Ctrl+l:清屏,相当于clear命令
Ctrl+a:跳转至命令开始处
Ctrl+e:跳转至命令结尾处
Ctrl+c:取消命令的执行
Ctrl+u:删除 命令行首 至 光标所在处 的所有内容
Ctrl+k:删除 光标所在处 至 命令行尾部 的所有内容
九、bash的的I/O重定向及管道
程序:指令+数据
读入数据:Input
输出数据:Output
Linux一切皆文件,内核识别设备文件需要id,因此打开的文件都有一个fd:file descriptor (文件描述符)
标准输入:keyborad, 0
命令本身无默认参数且输入命令时未给予参数,则从标准输入(键盘)中获取数据
标准输出:monitor, 1
命令的执行结果
标准错误输出:monitor, 2
命令的执行错误状态结果
e.g. cat不指定位置则从标准输入(键盘)中获取数据
I/O重定向:改变标准位置
1.输出重定向
COMMAND > NEW_POS
COMMAND >> NEW_POS
>:覆盖重定向,目标文件中的原有内容会被清除
>>:追加重定向,新内容会追加至目标文件尾部
问题:如何禁止对覆盖重定向
# set -C:禁止将内容覆盖输出至已有文件中
问题:系统已经禁止对覆盖重定向,如果还想覆盖呢
强制覆盖:>|
或# set +C: P.S. C要大写 (仅对当前shell有效)
2>:覆盖重定向错误输出数据流
2>>:追加重定向错误输出数据流
2.标准输出和错误输出各自定向至不同位置
COMMAND > /path/to/file.out 2> /path/to/error.out
3.合并标准输出和错误输出
两类标准输出合并为同一个数据流进行重定向
&>:覆盖重定向
&>>:追加重定向
COMMAND > /path/to/file.out 2> &1
COMMAND >> /path/to/file.out 2>> &1
4.输入重定向:<
不能接文件作为参数的命令,可以用输入重定向获取参数
命令:tr转换或删除字符 (tr不能接文件作参数)
tr [OPTION]... SET1 [SET2]
-d:把参数中的与SET1相同的字符都删去
不加参数:把字符集SET1 对位替换成SET2
P.S. 使用输入重定向时一般不会跟两个参数
<<结束字符 创建文档
# cat << EOF
# cat > /path/to/somefile << EOF
此处生成文档:从键盘键入一些字符保存到指定文档,遇到给定字符结束
5.管道
COMMAND1 | COMMAND2 | COMMAND3 |...
Note:最后一个命令会在当前shell进程的子shell进程中执行
命令:tee 既输出到屏幕又保存到指定路径下
tee [OPTION]... [FILE]... 一路输入,两路输出
练习
①将/etc/passwd文件中的前5行内容转换为大写后保存至/tmp/passwd文件中
$ head -5 /etc/passwd | tr 'a-z' 'A-Z' > /tmp/passwd
②将登录至将前系统上用户信息中的后3行的信息转换为大写后保存至/tmp/who.out文件中
# who | tail -n 3 | tr 'a-z' 'A-Z' > /tmp/who.out
十、提供了编程环境
1.程序:指令+数据
(算法+数据结构)
程序编程风格:
过程式:以指令为中心,数据服务于指令
对象式:以数据为中心,指令服务于数据
shell程序:提供了编程能力,解释执行
程序的执行方式:
计算机:运行二进制指令
编程语言:
低级:汇编
高级:
编译:高级语言-->编译器-->目标代码
C、C++, java
解释:高级语言-->解释器-->机器代码
shell, perl, python
过程式编程:
顺序执行
循环执行
选择执行
2.shell编程:过程式、解释执行
编程语言的基本结构:
数据存储:变量、数组
表达式
语句
shell,执行特性提供了基本语法控制,保留字之外,其他的语句、表达式、内部功能的实现,不是靠自己内部函数调用来实现,不像其他语言有自己的库,没有提供真正意义上的内建函数。shell依赖于当前系统环境
shell脚本是文本文件
shebang机制:执行脚本文件的解释器所在路径
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl
必须顶格写
magic number: 魔数控制机制,内核通过魔数判断文件格式
运行脚本:
1、给予执行权限,通过具体的文件路径指定文件执行
2、直接运行解释器,将脚本作为解释器程序的参数运行
问题:shell文本在什么条件下可以运行?
①有执行权限,且在PATH的路径之一
[[email protected] tmp]# bs.sh
②有执行权限但不在PATH中
a. [[email protected] tmp]# . bs.sh
b. [[email protected] tmp]# ./bs.sh
c. bash bs.sh 手动指定解释器,把bs.sh当做解释器的参数
e.g. chmod +x one.sh(添加执行权限)
相关概念
变量:命名的内存空间
数据存储方式
字符:110:24位二进制
数值:110 --> 8位二进制
变量的数据类型
作用:
①数据存储格式
②决定能够参与的运算
③表示的数据范围
类型:
①字符
②数值:
整型 浮点型
问题:浮点型数值的细节, 是如何存储和运算
①十进制浮点数转化为二进制浮点数,e.g. 8.125->1000.001b
具体过程:除二取余法(最后被除数为0)得到整数部分的二进制数,乘二取整(直到小数部分全为0)得到小数部分的二进制数,最后得到二进制小数1000.001
②得到的二进制小数如何在计算机中存储呢?
a. 先转化成二进制的科学计数法表示1.000.001*2^3
b. 指数位是采用移位(其中左移为正,右移为负)的存储方式,因而数值为移动的位数3+127,=130,为数后零补齐
c. float由左至右分三部分存储:1符号位+8指数位+23数位
③最终表示
0 10000010 00000100000000000000000
问题:二进制浮点数转化成十进制浮点数
①由指数位大于127还是小于127判断左移还是右移
②逆向移动的到未用科学技术法表示的二进制浮点数
③Am*2^a+Bn*(1/2)^b得到十进制数
编程程序语言:
强类型:不允许跨类型操作
弱类型: e.g. bash
把所有要存储的数据统统当作字符进行
不支持浮点数;
逻辑运算:布尔型bool
true, false
1, 0
状态结果的0表示成功
与:
1 && 1 = 1
1 && 0 = 0
0 && 1 = 0
0 && 0 = 0
或:
1 || 1 = 1
1 || 0 = 1
0 || 1 = 1
0 || 0 = 0
非:
! 1 = 0
! 0 = 1
异或
短路运算:
与:
第一个为0,结果必定为0
第一个为1,第二个必须要参与运算
或:
第一个为1,结果必定为1
第一个为0,第二个必须要参与运算
练习
①写一个脚本,实现如下功能
如果user1用户存在,就显示其存在,否则添加之
显示添加的用户的id号等信息
分析:如何判断id是否存在,通过程序状态结果
重定向不影响程序状态结果
利用短路运算,&& [success] || [fail]
#!/bin/bash
id user1 &> /dev/null && echo "user1 exists." || useradd user1
id user1
②写一个脚本,完成如下功能
如果root用户登录了当前系统,就显示root用户在线;否则说明其未登录
# w | grep "^root\>" &> /dev/null && echo "root logged" || echo "root not logged"
P.S. ^root\> 保证了root在第一字段,且排除了rootxxx的情况
十一、bash的变量
变量类型
3个作用(规定了) 数据存储格式、存储空间大小、参与运算种类
字符型
数值型:整型、浮点型
强类型:定义变量时必须指定类型、参与运算必须符合类型要求;调用未声明变量会产生错误
弱类型:无须指定类型,默认均为字符型
参与运算会自动进行隐式类型转换
变量无须事先定义可直接调用
变量命名法则
不能使程序中的保留字:例如if, for
只能使用数字、字母及下划线,且不能以数字开头
见名知义
bash中的变量的种类
根据变量的生效范围等标准分类
本地变量:生效范围为当前shell进程
对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效
环境变量:生效范围为当前shell进程及其子进程
局部变量:生效范围为当前shell进程中某代码片断(通常指函数)
在某函数生命周期时有效
位置变量:调用通过命令行传递给它的参数,用$1, $2, ... 来表示,用于让脚本在脚本代码中
特殊变量:$?, $0, $*, [email protected], $#
1.本地变量
变量赋值:name='value'
可以使用引用,value可以是:
① 可以是直接字串:name="username"
② 变量引用: name="$username"
name=$username
③ 命令引用: name=`COMMAND`
name=$(COMMAND)
P.S. echo name
命令引用,不加双引号,输出结果不换行
变量引用:${name}, $name
"":弱引用,其中的变量引用会被替换为变量值
'':强引用,其中的变量引用不会被替换为变量值,而保持原字符串
命令:set 显示已定义的所有变量
命令:unset name 销毁变量
2.环境变量
变量声明、赋值: export name=VALUE
declare -x name=VALUE 定义环境变量
把变量导入到环境变量
变量引用:${name}, $name
命令:export, env, printenv 显示所有环境变量
命令:unset name 销毁变量
命令:pstree 显示进程树
问题:bash有哪些内建的环境变量?
SHELL, UID, PS1, HOME
PATH, PWD, OLD
HISTSIZE, HISTFILE
... ...
PS1 定义了命令提示符前的内容
只读变量
类似于常量,不能修改和撤销
命令:readonly name
命令:declare -r name
3.位置变量
在脚本代码中调用通过命令行传递给脚本的参数
$1, $2, ...:对应调用第1、第2等参数
4.特殊变量
$?:上条命令的执行状态结果
$0:命令本身
$*:传递给脚本的所有参数,所有参数当一个字符串
[email protected]:传递给脚本的所有参数,把每一个参数当成独立字符串
$#:传递给脚本的参数的个数
(脚本)判断给出的文件的行数
#!/bin/bash
LineCount="$(wc -l $1| cut -d' ' -f1)"
echo "$1 has $LineCount lines."
命令:shift [n]
十二、bash的配置文件
1.按生效范围划分
全局配置:
/etc/profile()登录Shell的时候读取
/etc/profile.d/*.sh
/etc/sysconfig/i18n
/etc/bashrc
/etc/profile(交互式登录的shell才会读取)
设置的主要变量有PATH, MAIL, USER, HOSTNAME, HISTSIZE
/etc/profile.d/*.sh(可通过创建.sh文件设置共享的命令别名)
/etc/sysconfig/i18n(由/etc/profile.d/lang.sh调用,主要设置变量LANG)
/etc/bashrc(非交互式登录的shell读取)
定义了:依据不同UID规定umask, 提示符(PS1变量),调用/etc/profile.d/*.sh
个人配置:
~/.bash_profile
~/.bashrc
~/.bash_profile(交互式登录的shell才会读取)
在bash读取完/etc/parofile及其调用的其他配置文件后,读取~/.bash_profile,可设置PATH,通过export PATH将其设置为环境变量
~/.bashrc(非交互式登录的shell读取)
由于~/.bashrc文件会调用/etc/bashrc,所以如果没有~/.bashrc,则bash提示符会变成类似于:-bash-3.2$,解决办法,复制/etc/skel/.bashrc到家目录下,再source ~/.bashrc
2.按功能划分
profile类:主要为交互式登录的shell提供配置
全局:/etc/profile, /etc/profile.d/*.sh
个人:~/.bash_profile
作用:
①用于定义环境变量
②运行命令或脚本
bashrc类:主要为非交互式登录的shell提供配置
全局:/etc/bashrc
个人:~/.bashrc
作用:
①定义命令别名
②定义本地变量
命令:读入环境配置文件source /path/to/config_file
或 . /path/to/config_file
shell登录
交互式登录: ①直接通过终端输入账号密码登录
②使用“su - UserName”或“su -l UserName”切换的用户
读取顺序
/etc/profile --> /etc/profile.d/*.sh --> ~/.bash_profile --> ~/.bashrc --> /etc/bashrc
非交互式登录: su UserName
图形界面下打开的终端
执行脚本
读取顺序
~/.bashrc --> /etc/bashrc --> /etc/profile.d/*.sh
编辑配置文件定义的新配置的生效方式
① 重新启动shell进程
② 使用source或.命令进程
问题: a. 在哪个配置文件可以定义对所有用户都生效的别名?
/etc/bashrc
b. 在哪个配置文件可以定义对所有用户都生效的登陆提示?
/etc/profile.d/*.sh
c. 在哪个配置文件设置可以让用户的PATH环境变量的值多出一个路径,例如多如/usr/local/apache2/bin
仅对管理员 export PATH="$PATH:/usr/local/apache2/bin"
/root/.bashrc
对所有 /etc/bashrc 在/etc/profile.d/下创建一个内容为export PATH="$PATH:/usr/local/apache2/bin"的.sh名结尾的文件
十三、bash的算术运算
相关运算符
+, -, *, /, %, **
&, &&, |, ||, !
1.实现算术运算的方式
① let var=算术表达式,*不需要转义
② var=$[算术表达式] ,*不需要转义
③ var=$((算术表达式)) ,*不需要转义
④ var=$(expr arg1 arg2 arg3 ...),*需要转义
乘法符号有些场景中需要转义
bash有内建的随机数生成器:$RANDOM
2.增强型赋值
+=, -=, *=, /=, %=
let var OPER value
e.g. let count+=1
自增,自减
let var+=1
let var++
let var-=1
let var--
练习
①(脚本)计算/etc/passwd文件中的第10个用户和第20用户的ID之和
#!/bin/bash
userid1=$(head -n 10 /etc/passwd | tail -n 1 | cut -d:-f3)
userid2=$(head -n 20 /etc/passwd | tail -n 1 | cut -d:-f3)
useridsum=$[$userid1+$userid2]
echo "uid sum:$useridsum"
②(脚本)传递两个文件路径作为参数给脚本,计算这两个文件中所有空白行之和
#!/bin/bash
spaceline1=$(grep "^[[:space:]]*$" $1 | wc -l)
spaceline2=$(grep "^[[:space:]]*$" $2 | wc -l)
echo "The sum of space line:$[$spaceline1+$spaceline2]"
③统计/etc, /var, /usr目录共有多少个一级子目录和文件
十四、bash的条件测试
条件测试:
判断某需求是否满足,需要由测试机制来实现
P.S. 专用的测试表达式需要由测试命令辅助完成测试过程
测试方式
test EXPRESSION
[ EXPRESSION ]
[[ EXPRESSION ]]
Note:EXPRESSION前后必须有空白字符
测试表达式的类别
1.数值测试
-gt:是否大于
-ge:是否大于等于
-eq:是否等于
-ne:是否不等于
-lt:是否小于
-le:是否小于等于
2.字符串测试
==:是否等于 也可以用=
P.S. ==号两边要有空格,否则会被识别成赋值
>:是否大于
<:是否小于
!=:是否不等于
=~:左侧字符串是否能够被右侧的PATTERN所匹配,测试条件的组合
Note:此表达式一般用于[[ ]]中
-z "STRING":测试字符串是否为空,空则为真,不空则为假
-n "STRING":测试字符串是否不空,不空则为真,空则为假
Note:用于字符串比较时的用到的操作数都应该使用引号
P.S. 模式匹配可以不用全串匹配,e.g. ^t部分匹配tudou也执行成功
3.文件测试
①存在性测试
-a FILE:文件存在性测试
-e FILE:文件存在性测试,存在为真,否则为假
②存在性及类别测试
-b FILE:是否存在且为块设备文件
-c FILE:是否存在且为字符设备文件
-d FILE:是否存在且为目录文件
-f FILE:是否存在且为普通文件
-h FILE 或 -L FILE:存在且为符号链接文件
-p FILE:是否存在且为命名管道文件
-S FILE:是否存在且为套接字文件
问题:为什么要做文件测试?
写脚本时,事先判断某个文件在不在,再才进行后续操作
③文件权限测试
取决于当前用户
-r FILE:是否存在且可读
-w FILE:是否存在且可写
-x FILE:是否存在且可执行
④文件特殊权限测试
-g FILE:是否存在且拥有sgid权限
-u FILE:是否存在且拥有suid权限
-k FILE:是否存在且拥有sticky权限
⑤文件大小测试
-s FILE:是否存且非空
⑥文件是否打开
-t fd:fd表示文件描述符是否已经打开且与某终端相关
-N FILE:文件自动上一次被读取之后是否被修改过
-O FILE:当前有效用户是否为文件属主
-G FILE:当前有效用户是否为文件属组
⑦双目测试
FILE1 -ef FILE2:FILE1与FILE2是否指向同一个设备上的相同inode
nt:new than ot:old than
FILE1 -nt FILE2:FILE1是否新于FILE2
FILE1 -ot FILE2:FILE1是否旧于FILE2
P.S. 备份时用到
e.g. [ -e /tmp/test ] || mkdir /tmp/test
4.组合测试条件
第一种方式:
COMMAND1 && COMMAND2
COMMAND1 || COMMAND2
! COMMAND
e.g. [ -e FILE ] && [ -r FILE ]
第二种方式:
EXPRESSION1 -a EXPRESSION2
EXPRESSION1 -o EXPRESSION2
! EXPRESSION
P.S. 必须使用测试命令进行
十五、bash自定义退出状态码
命令:exit [n]:自定义退出状态码
注意: 脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字n
如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码
练习
(脚本)接受一个文件路径作为参数
如果参数个数小于1,则提示用户“至少应该给一个参数”,并立即退出
如果参数个数不小于1,则显示第一个参数所指向的文件中的空白行数
[ $# -lt 1 ] && echo "至少给一个参数" && exit 2 || echo " The space line is $(grep "^$" $1 | wc -l)"
回顾
bash的基础特性:
命令补全:$PATH
路径补全
命令历史:history
命令行展开: ~, {}
命令的状态结果
成功:0
失败:1-255
命令行展开:~, {}
命令别名:alias/unalias
glob:*, ?, [], [^]
快捷键:Ctrl+{a,e,l,c,u,k}
命令hash:hash
IO重定向、管道:
标准输出重定向:>, >>
标准错误重定向:2>, 2>>
&>, &>>
COMMAND > /path/to/somefile.out 2> &1
输入重定向:<
显式使用:tr
隐式使用:...
管道:COMMAND1 | COMMAND2 | ...
shell:
#!/bin/bash
过程式:以指令为中心
对象式:以数据为中心
配置文件:profile, bashrc
算术运算:
let, $[], $(()), expr
+=, -=, *=, /=
++, --
条件测试:test, [], [[]]
三种:
数值:-lt, -le, -gt, -ge, -ne, -eq
字符串:==, !=, >, <, =~, -z, -n
文件
单目:-e, -f, -d, -b, -c, -L, -P, -S, -r, -w, -x, -s
双目:-nt, -ot
组合测试:-a, -o, !
自定义退出码:
exit [n]
参考资料:
马哥随堂笔记
注:诚恳欢迎读者对本文提出批评意见,若发现存在错误,我定第一时间修改。如果读者觉得文章对您有帮助,欢迎点赞鼓励一下哟٩(๑❛ᴗ❛๑)۶。