什么是 Shell
-
Shell 是命令解释器,用于解释用户对操作系统的操作
-
shell 有很多, 查看机器支持的 shell
cat /etc/shells
Linux 的启动过程
/BIOS-MBR-BootLoader(grub)-kernel-init-系统初始化-shell
Shell 书写规范
#!/bin/bash
# Sha-Bang
# 第一行的#!/bin/bash,告诉看脚本的人,这个文件需要用哪个shell编译器运行
# 当以 ./xx.sh 的方式运行脚本是,会读取第一行,并以对应的编译器运行
# demo
cd /var
ls
pwd
# 列出当前目录下所有文件大小
du -sh *
# 查看当前文件夹的大小
du -sh
# 查看整文件系统空间使用情况
df -h
执行方式
-
bash ./filename.sh : 产生一个bash的子进程运行
- ./filename.sh : 使用Sha-Bang 对应的解析器解析,并生成一个子进程运行
- source [./]filename.sh : 在当前进程运行
- . filename.sh : source 的简化写法, 在当前进程运行
内建命令和外部命令的区别
- 内建命令不需要创建子进程, 比如cd, 会切换当前进程目录
- 内建命令对当前 Shell 生效
管道与重定向
- 管道与管道符
- 管道和信号一样,也是进程通信的方式之一
- 匿名管道(管道符)是 Shell 编程经常用到的通信工具
管道符是“|”,将前一个命令执行的结果传递给后面的命令, 管道符的两边是两个子进程
ps | cat
echo 123 | ps
- 子进程与子shell
- 子进程是 Shell 程序,称作子 Shell ?
- 内部命令的结果不会传递给子 Shell ?
- 重定向符号
- 一个进程默认会打开标准输入、标准输出、错误输出三个文件描述符
-
输入重定向符号 “ <”
- read var < /path/to/a/file : 把文件的内容输入到前面的进程
- wc -l < /etc/passwd : 统计文件行数
- 输出重定向符号 “>” “»” “2>” “&>”
-
”>” : echo 123 > test , 输出并覆盖文件内容
-
”»”: echo 123 » test , 输出内容到文件末尾
-
“2>”: mkdi 2> test , 输出错误结果到文件
-
“&>”: mkdi &> test , 无论输出有没有出错,都输出到文件
-
组合应用: cat > ~/Desktop/test.sh «EOF 开始文件编辑, 遇到EOF文件结束. 用于在shell内部再生成shell文件
#!/bin/bash cat > ~/Desktop/test.sh <<EOF echo "hello bash" EOF
-
变量
-
变量的定义
-
变量的赋值
-
变量的查看
$var
,${var}
-
数组
# 定义数组 IPTS=(10.0.0.1 10.0.0.2 10.0.0.3 ) # 显示数组的所有元素 echo ${IPTS[@]} # 显示数组元素个数 echo ${#IPTS[@]} # 显示数组的第一个元素 echo ${IPTS[O]}
-
变量的作用范围
- 默认作用范围,当前进程
- 使用
source subShell.sh
运行的脚本因为是在当前进程运行的,所以可以获得上去上下文的变量 export var1=xxx
可以导出变量, 子进程也可以使用var1
unset var1
释放变量,将它置为空
-
系统环境变量
-
env
: 获取系统环境变量 -
set
: 获取当前环境定义的变量 -
$?
: 上一条指令运行正确返回 0, 错误是1# 目录 Desktop 存在 ➜ ~ test -d Desktop ➜ ~ echo $? 0 # 目录 Deskto 不存在 ➜ ~ test -d Deskto ➜ ~ echo $? 1
-
$$
: 当前进程的pidecho $$ 1033
-
$0
: 当前进程名, 也是当前运行脚本的文件名echo $0 -zsh
$#
: 环境变量的个数${2-_}
: 处理空参数的技巧➜ bash_test cat 8.sh #!/bin/bash # demo pos1=$1 # 没有读到参数的时候用‘_’占位 pos2=${2-_} echo $pos1 echo $pos2 ➜ bash_test sh 8.sh -a -l -a -l ➜ bash_test sh 8.sh -a -a _
-
-
环境变量配置文件
-
/etc/profile , ~/.bash_profile :
su - user
的时候才会调用 -
/etc/bashrc , ~/.bashrc :
su user
的时候调用$ su root ~/.bashrc /etc/bashrc $ su - root /etc/profile ~/.bash_profile ~/.bashrc /etc/bashrc
-
-
运算符
- 赋值运算符: =
- 使用
unset
取消为变量的赋值 - = 除了作为赋值运算符还可以作为测试操作符
- 使用
- 算数运算符: + - * / % **
- 使用expr计算:
expr 4 * 5
- 使用(())计算:
(( 4 ** 5 ))
,((4**5))
- 使用expr计算:
- 数字常量:
- let 常量名=变量值 -> 不使用let定义变量, 用0开头就只是字符串
- 变量值使用0开头为八进制
- 变量值使用0x开头为十六进制
- 双圆括号
- 双圆括号是let命令的简化 :
let a=0x78
等价于((a=0x78))
- (( a= 10 ))
- (( a++ ))
- echo $((2**10))
- 双圆括号是let命令的简化 :
特殊符号大全
-
引号:
单引号 ‘
: 完全引用,“$var” $不能获取变量值
双引号“
: 不完全引用,“$var” $可以获取变量值
反引号
` : 执行命令
-
括号
-
()(())$()
圆括号- (): 单独使用圆括号会产生一个子shell (xyz=123) : 子Shell和子线程不是一回事
- (): 数组初始化 IPS=( ip1 ip2 ip3 )
- (( )) : 计算括号内的公式, 等价于 let
- $() : 相当于 ``, 括号内写命令
-
[] [[]]
方括号-
单独使用方括号是测试(test)或数组元素功能
var=(1 2 3) # 取数组元素 echo ${var[0]} 1 echo ${var[1]} 2 echo ${var[2]} 3 # 测试判断, [] 内需要使用 -le, -ge ... 等比较符号 [root@3fc93cd751e9 learn_bash]# [ 1 -le 2 ] [root@3fc93cd751e9 learn_bash]# echo $? 0 [root@3fc93cd751e9 learn_bash]# [ 1 < 2 ] bash: 2: No such file or directory [root@3fc93cd751e9 learn_bash]# [ 3 -le 2 ] [root@3fc93cd751e9 learn_bash]# echo $? 1
-
两个方括号表示测试表达式
[root@3fc93cd751e9 learn_bash]# [[ 3 > 1 ]]; echo $? 0 [root@3fc93cd751e9 learn_bash]# [[ 3 < 1 ]]; echo $? 1
-
< > 尖括号: 重定向符号
-
{ } 花括号
-
输出范围echo {0..9}
-
文件复制cp /etc/passwd{,.bak} -> {a,b} 表示一个序列 a 和 b.
所以前面的句子相当于
cp /etc/passwd /etc/passwd.bak
[root@3fc93cd751e9 learn_bash]# echo {0..9} 0 1 2 3 4 5 6 7 8 9 [root@3fc93cd751e9 learn_bash]# touch name [root@3fc93cd751e9 learn_bash]# cp name{,.bak} [root@3fc93cd751e9 learn_bash]# ll total 4 -rw-r--r-- 1 root root 0 Jul 8 14:09 name -rw-r--r-- 1 root root 0 Jul 8 14:10 name.bak [root@3fc93cd751e9 learn_bash]# cp name{.bak,.book} [root@3fc93cd751e9 learn_bash]# ll total 4 -rw-r--r-- 1 root root 0 Jul 8 14:09 name -rw-r--r-- 1 root root 0 Jul 8 14:10 name.bak -rw-r--r-- 1 root root 0 Jul 8 14:10 name.book
-
-
-
-
运算和逻辑符号
-
+-*/%
算数运算符 -
><=
比较运算符 -
&& || !
逻辑运算符[root@3fc93cd751e9 learn_bash]# (( 5 > 4)); echo $? 0 [root@3fc93cd751e9 learn_bash]# (( 5 < 4)); echo $? 1 [root@3fc93cd751e9 learn_bash]# (( 5 > 4 && 6 > 5 )); echo $? 0 [root@3fc93cd751e9 learn_bash]# (( 5 > 4 && 6 < 5 )); echo $? 1 [root@3fc93cd751e9 learn_bash]# (( 5 > 4 || 6 < 5 )); echo $? 0 [root@3fc93cd751e9 learn_bash]# (( ! 5 > 4 )); echo $? 1
-
-
转义符号
-
其他符号
-
#注释符
-
;命令分隔符
- case语句的分隔符要转义 ;;
-
:空指令
-
-
.和 source命令相同
- ~ 家目录
-
,分隔目录
# 查看当前目录下所有文件及子目录的内容 [root@3fc93cd751e9 ~]# ls * anaconda-ks.cfg anaconda-post.log docker helloworld.sh original-ks.cfg learn_bash: name name.bak name.book subShell.sh
测试与判断
退出与退出状态
- 退出程序命令
- exit : 不带数字使用会返回上一条指令执行返回的错误码,0表示正常退出
- exit 10 返回10给 Shell,返回值非 0 位不正常退出
- $? 判断当前 Shell 下前一个进程是否正常退出
测试命令 test
-
test 命令利用程序是否正常退出返回 0 或 1
-
test 可以做以下测试:
- 文件测试
- 整数比较测试
- 字符串测试
# 查看test的用法 man test SYNOPSIS test expression [ expression ] DESCRIPTION -d file True if file exists and is a directory. -e file True if file exists (regardless of type). -f file True if file exists and is a regular file. string True if string is not the null string. s1 = s2 True if the strings s1 and s2 are identical. s1 != s2 True if the strings s1 and s2 are not identical. s1 < s2 True if string s1 comes before s2 based on the binary value of their characters. s1 > s2 True if string s1 comes after s2 based on the binary value of their characters. n1 -eq n2 True if the integers n1 and n2 are algebraically equal. n1 -ne n2 True if the integers n1 and n2 are not algebraically equal. n1 -gt n2 True if the integer n1 is algebraically greater than the integer n2. n1 -ge n2 True if the integer n1 is algebraically greater than or equal to the integer n2. n1 -lt n2 True if the integer n1 is algebraically less than the integer n2. n1 -le n2 True if the integer n1 is algebraically less than or equal to the integer n2. These primaries can be combined with the following operators: ! expression True if expression is false. expression1 -a expression2 True if both expression1 and expression2 are true. expression1 -o expression2 True if either expression1 or expression2 are true. ( expression ) True if expression is true. EXIT STATUS The test utility exits with one of the following values: 0 expression evaluated to true. 1 expression evaluated to false or expression was missing. >1 An error occurred
test案例
[root@3fc93cd751e9 learn_bash]# ls exitDemo.sh name name.bak name.book subShell.sh [root@3fc93cd751e9 learn_bash]# test -e exitDemo.sh ; echo $? 0 [root@3fc93cd751e9 learn_bash]# test -e exitDe.sh ; echo $? 1 [root@3fc93cd751e9 learn_bash]# test -d ../learn_bash ; echo $? 0 [root@3fc93cd751e9 learn_bash]# test -f ../learn_bash ; echo $? 1 [root@3fc93cd751e9 learn_bash]# [ 3 -gt 2 ]; echo $? 0 [root@3fc93cd751e9 learn_bash]# [ 1 -gt 2 ]; echo $? 1 [root@3fc93cd751e9 learn_bash]# [ "name" ]; echo $? 0 [root@3fc93cd751e9 learn_bash]# [ "name" = 'name' ]; echo $? 0 [root@3fc93cd751e9 learn_bash]# [ "name" = 'name1' ]; echo $? 1 [root@3fc93cd751e9 learn_bash]# [ "name" < 'name1' ]; echo $? bash: name1: No such file or directory 1 [root@3fc93cd751e9 learn_bash]# [ "name" -le 'name1' ]; echo $? bash: [: name: integer expression expected 2 [root@3fc93cd751e9 learn_bash]# [[ "name" < 'name1' ]]; echo $? 0 [root@3fc93cd751e9 learn_bash]# [[ "namea" < 'name1' ]]; echo $? 1 [root@3fc93cd751e9 learn_bash]# [ 1 -lt 3 -a 4 -le 5 ]; echo $? 0 [root@3fc93cd751e9 learn_bash]# [ 1 -lt 3 -a 4 -ge 5 ]; echo $? 1 [root@3fc93cd751e9 learn_bash]# [ 1 -lt 3 -o 4 -ge 5 ]; echo $? 0 [root@3fc93cd751e9 learn_bash]# [ 1 -lt 3 ]; echo $? 0 [root@3fc93cd751e9 learn_bash]# [ ! 1 -lt 3 ]; echo $? 1
使用 if-then语句
-
test 测试语句可以简化为 [ ] 符号
-
if-then 语句的基本用法
if [ 测试条件成⽴ 或 命令返回0 ] ; then 执行相应命令 fi 结束 # 或如下写法 if [ 测试条件成⽴ 或 命令返回0 ] then 执行相应命令 fi 结束
使用 if-then-else 语句
-
if-then-else 语句可以在条件不成立时也运行相应的命令
if [ 测试条件成立 ] then 执行相应命令 else 测试条件不成⽴,执行相应命令 fi 结束 # 或如下写法 if [ 测试条件成立 ]; then 执行相应命令 else 测试条件不成⽴,执行相应命令 fi 结束 if [ 测试条件成立1 ]; then 执行相应命令 elif [ 测试条件成立2 ]; then 执行相应命令 else 测试条件不成⽴,执行相应命令 fi 结束
嵌套 if 的使用
- if 条件测试中可以再嵌套 if 条件测试
- 嵌套的结果和复合比较语句 && 结果相同
分支
-
case语句和select语句可以构成分支
case"$变量” in “情况1”) 命令... ;; “情况2”) 命令... ;; * ) 命令... ;; esac
案例
#!/bin/bash # case demo name=$1 case $name in "april"|"APRIL") # april or APRIL echo "hello april" ;; ang*) # ang wildcard echo "hello angie" ;; *) echo "hello nobody" ;; esac
循环
使用 for 循环遍历命令的执行结果
-
for 循环的语法
for 参数 in 列表 do 执行的命令 done 封闭一个循环
-
使用反引号或 $() 方式执行命令,命令的结果当作列表进行处理
# 获取当前文件夹下面 x.mp3 文件列表,遍历它 for filename in `ls *.mp3` do # 把mp3改成mp4 # $(basename $filename .mp3) 取出文件去掉.mp3之后的名字 mv $filename $(basename $filename .mp3).mp4 done
C 语言风格的 for 命令
for((变量初始化;循环判断条件;变量变化))
do
循环执行的命令
done
for (( i=1 ; i <= 10 ; i++ ))
do
echo $i;
done
while 循环
while test测试是否成⽴
do
命令
done
➜ bash_test a=1
➜ bash_test while [ $a -lt 10 ]; do
echo $a ; ((a++))
done
1
2
3
4
5
6
7
8
9
➜ bash_test echo $a
10
until 循环
-
until 循环与 while 循环相反,循环测试为假时,执行循环,为真时循环停止
until test测试是否成⽴ do 命令 done ➜ bash_test a=1 ➜ bash_test until [ $a -ge 10 ];do until> echo $a; ((a++)) until> done 1 2 3 4 5 6 7 8 9
循环的使用
* 循环和循环可以嵌套
* 循环中可以嵌套判断,反过来也可以实现嵌套
* 循环可以使用 break 和 continue 语句在循环中退出
# break
➜ bash_test for i in {1..9};do
if [ $i = 5 ];then
break;
fi
echo $i
done
1
2
3
4
# continue, skip 5
➜ bash_test for i in {1..9};do
if [ $i = 5 ];then
continue;
fi
echo $i
done
1
2
3
4
6
7
8
9
使用循环处理命令行参数
- 命令行参数可以使用 $1 $2 … ${10}… $n 进行读取
-
$0 代表脚本名称
$* 和 $@ 代表所有位置参数
- $# 代表位置参数的数量
使用 ${1-_} 方式代替 $1 避免变量为空导致的遗产
#!/bin/bash
# help display help help
for pos in $@;do
if [ "$pos" = "help" ]; then
echo $pos $pos
else
echo $pos
fi
done
echo "1:"$1
# 参数为空的时候占位
echo "1_:"${1-_}
# shift 移动参数,每次删掉参数列表的第一个参数
# 配合while [ $# -ge 1 ] 可以便利参数列表
while [ $# -ge 1 ]
do
if [ "$1" = "help" ]; then
echo $1 $1
fi
shift
done
函数
自定义函数
-
函数用于“包含”重复使用的命令集合
-
自定义函数
# function 可以省略 function fname(){ 命令 } ## 函数的执行 fname
-
函数作用范围的变量
- local 函数名 : 定义局部变量,避免污染全局变量
-
函数参数
$1 $2 $3 ... $n
自建函数库
- 使用 source 函数脚本文件“导入”函数 cd
脚本控制
脚本优先级控制
-
可以使用nice和renice调整脚本优先级
-
避免出现“不可控的”死循环
- 死循环导致cpu占用过高
- 死循环导致死机
➜ ~ ulimit -a -t: cpu time (seconds) unlimited -f: file size (blocks) unlimited -d: data seg size (kbytes) unlimited -s: stack size (kbytes) 8192 -c: core file size (blocks) 0 -v: address space (kbytes) unlimited -l: locked-in-memory size (kbytes) unlimited -u: processes 2784 -n: file descriptors 256
fork炸弹: 函数递归调用,cpu满了
func() { func | func& }; func # 或 .() { . | .& }; . bash: fork: retry: Resource temporarily unavailable bash: fork: retry: No child processes bash: fork: retry: No child processes bash: fork: retry: Resource temporarily unavailable bash: fork: retry: No child processes bash: fork: retry: No child processes bash: fork: retry: Resource temporarily unavailable ...
捕获信号
-
捕获信号脚本的编写
- kill默认会发送15号信号给应用程序
- ctrl+c发送2号信号给应用程序
- 9号信号不可阻塞
vim 16.sh #!/bin/bash # signal demo # 捕获 15, 不会被kill掉 trap "echo sig 15" 15 # 捕获 2, 不会被ctrl+c结束进程 trap "echo sig 2" 2 ## 打印当前进程 echo $$ while : do : done [root@3fc93cd751e9 learn_bash]# bash 16.sh 866 ^Csig 2 ^Csig 2 sig 15 Killed ## 只能被kill -9 866 杀掉
计划任务
一次性计划任务 at
[root@3fc93cd751e9 learn_bash]# at 09:03
warning: commands will be executed using /bin/sh
at> echo hello > /tmp/hello.txt
at> <EOT>
job 2 at Sat Jul 10 09:03:00 2021
# 使用ctrl+D结束输入,并提交
[root@3fc93cd751e9 learn_bash]# atq
2 Sat Jul 10 09:03:00 2021 a root
周期性计划任务
-
cron
- 配置方式: crontab -e
- 查看现有的计划任务: crontab -l
- 配置格式:
- 分钟 小时 日期 月份 星期执行的命令
- 注意命令的路径问题
➜ /tmp crontab -l crontab: no crontab for april ➜ /tmp crontab -e crontab: no crontab for april - using an empty one crontab: installing new crontab ➜ /tmp crontab -l * * * * * date >> /tmp/date.txt ➜ /tmp tail -f date.txt Sat Jul 10 17:19:00 CST 2021 Sat Jul 10 17:20:00 CST 2021
计划任务加锁
- 如果计算机不能按照预期时间运行
- anacontab 延时计划任务
- flock 锁文件
# 排他锁, 使这种方式运行, 如果当前运行的a.sh没有结束
# 就不能在次运行
flock -xn "/tmp/f.lock" -c "/root/a.sh"
Vim 文本编辑器的使用
四种模型:
- 正常模式(Normal-mode)
- HJKL, 光标移动, 左下右上, 记忆方式:前两个左下, 后两个上右
- copy-paste
- yy,p: yy复制当前行, p ->paste
- nyy, p: 复制n行
- y$,p: 复制当前行,插入到某行中间, 上面的复制方式都只能不能插入到行中间
- cut-paste
- dd,p: dd剪切当前行, p ->paste
- d$,p: 剪切当前行,,插入到某行中间, 上面的剪切方式都只能不能插入到行中间
- u, ctrl+r: u-> undo 撤销操作, ctrl+r -> redo 撤销的反操作
- x, X: 都是删除光标所在位置的一个字符
- r, R: r替换光标所在位置的一个字符, R进入替换模式,可以替换多个字符直到esc退出
- gg, shift+g, ngg: gg 跳到第一行, shift+g 跳到最后一行, ngg 跳到第n行
^ , $
: ^ 跳到行首, $ 跳到行尾
- 插入模式(Insert-mode)
- i, o, a
- 命令模式(Command-mode)
- :w filename -> 保存文件到某个位置
- :!cmd -> 在vim里边运行命令, 可以粘贴命令的运行结果
- 文本替换
- :s/old/new: 替换当前行第一个old -> new
- :%s/old/new: 所有行第一个old -> new
- :%s/old/new/g: 替换全文 old -> new
- :n,ms/old/new/g: 替换n行到m行中所有 old -> new
- /str -> 全文搜索str, n下一个, shift+N上一个
- :set nohlsearch -> 取消搜索高亮
- :set nonu, set nu -> 取消行号,添加行号
- vim /etc/vimrc -> 修改vim的启动基本配置
- 可视模式(Visual-mode)
- v 字符可视模式
- V 行可视模式
- ctrl+v 块可视模式
- 配合y,d和shift+i命令可以进行块的便利操作
- y,d 复制和剪切,在所有块模式下都可以用
- shift+i 在块模式下可以实现多行插入, 编辑完成之后esc按两次