一、Shell基础概念
1、Shell概念
shell本意是指壳,在计算机术语中,shell是指用户操作接口的意思。操作系统运行起来后都会给用户提供一个操作接口,这个操作接口就叫shell。用户可以通过shell来调用操作系统内部的复杂实现。
2、Shell发展
1971年,Ken Thompson(来自贝尔实验室)为UNIX开发了第一个shell,称为 V6 shell
Stephen Bourne 在贝尔实验室为 V7 UNIX所开发的Bourne shell,即sh
开源组织GNU为了取代Bourne shell开发的Bourne-Again shell,即Bash
3、Shell构成
二、Shell命令和语法
1、变量
| 类型 | 作用域 | 声明方式 | 规范 |
|---|---|---|---|
| 自定义变量 | 当前shell | =(隐式声明) | 字符串、整型、浮点型、日期型 |
| 环境变量 | 当前shell及其子shell | export declare -x(显示声明) | |
| 系统环境变量 | 所有shell | 启动加载 |
2、自定义变量
#!/bin/bash
# 这是脚本文件的shebang,它告诉系统脚本文件使用哪种解释器来执行。在这个例子中,使用的是bash解释器
#变量名=变量值(等号左右不能有空格),定义了两个变量page_size和page_num,并分别给它们赋值为1和2
page size=1
page_num=2
#将命令复制给变量,这样可以通过变量名来执行命令。
_ls=ls
#将命令结果赋值给变量,可以通过变量名来访问命令结果
file_list=$(ls -a)
#默认字符串,不会进行 + 运算。所以total的值为"page_size*page_num"字符串
total=page_size*page_num
#声明变量为整型(-i),后续的数值计算中,total会被当作整型来处理
let total=page_size*page_num
declare -i total=page_size*page_num
#导出环境变量,使得其在子进程中也可用
export total
declare -x total
3、系统环境变量
$#表示当前脚本或函数的参数个数。$*表示当前脚本或函数的所有参数列表。$?表示上一个命令的退出状态码。$PS1表示shell提示符的格式。$HOME表示当前用户的主目录。$PATH表示系统的可执行文件路径列表。
4、配置文件加载
5、运算符和引用
6、管道
管道与管道符 ,作用是将前一个命令的结果传递给后面的命令
语法:cmd1 | cmd2
要求:管道右侧的命令必须能接受标准输入才行,比如grep命令,ls、mv等不能直接使用,可以使用xargs预处理
注意:管道命令仅仅处理stdout,对于stderr会予以忽略,可以使用set -o pipefail设置shell遇到管道错误退出。
#!/bin/bash
#该命令将文件platform.access.log的内容通过管道符|传递给grep命令,用于查找文件中包含字符串ERROR的行。这个命令的作用是在platform.access.log文件中查找错误信息
cat platform.access.log | grep ERROR
#该命令使用netstat命令来显示所有的网络连接状态,并通过管道符|将结果传递给grep命令,用于查找所有处于ESTABLISHED状态的连接。最后,通过管道符|将结果传递给wc -l命令,用于统计行数。这个命令的作用是统计当前系统中所有处于ESTABLISHED状态的网络连接数量
netstat -an grep ESTABLISHED | wc -l
#该命令使用find命令在当前目录中查找所有扩展名为.sh的文件,并通过管道符|将结果传递给xargs命令,将查找到的文件名作为参数传递给ls -l命令,用于显示这些文件的详细信息。这个命令的作用是列出当前目录中所有扩展名为.sh的文件的详细信息
find . -maxdepth l -name "*.sh" | xargs ls -l
7、重定向
输出重定向符号:
>:覆盖写入文件
>>:追加写入文件
2>:错误写入文件
&>:正确和错误输出统一到文件中
输入重定向符号:
<
<<
8、判断命令
shell中提供了test、[、[[三种判断符号,可用于:
- 整数测试
- 字符串测试
- 文件测试
语法:
- test condition
- [ condition ]
- [[ condition ]]
注意:
- 中括号前后要有空格符
- [ 和test是命令,只能使用自己支持的标志位,<、>、=只能用来比较字符串
- 中括号内的变量,最好都是用引号括起来
- [[更丰富,在整型比较中支持<、>、=,在字符串比较中支持=~正则
#!/bin/bash
# 整数测试
test $nl -eq $n2 # 测试 $nl 是否等于 $n2
test $nl -lt $n2 # 测试 $nl 是否小于 $n2
test $nl -gt $n2 # 测试 $nl 是否大于 $n2
# 字符串测试
test -z $str_a # 测试 $str_a 是否为空
test -n $str_a # 测试 $str_a 是否非空
test $str_a = $str_b # 测试 $str_a 是否等于 $str_b
# 文件测试
test -e /dmt && echo "exist" # 测试 /dmt 是否存在,并在存在时输出 "exist"
test -f /usr/bin/npm && echo "file exist" # 测试 /usr/bin/npm 是否存在,并在存在时输出 "file exist"
9、分支语句
- if语句用于判断条件是否成立,如果成立则执行相应的命令或者代码块。
if语句的基本语法如下:
if [ condition ]
then
command1
command2
......
commandN
fi
# condition是需要判断的条件,可以是变量、字符串、数值等,command1到commandN是需要执行的命令或者代码块
- case语句用于根据不同的条件执行相应的命令或者代码块。
case variable in
pattern1)
command1
;;
pattern2)
command2
;;
......
*)
commandN
;;
esac
# variable是需要判断的变量,pattern1到patternN是需要判断的模式,command1到commandN是需要执行的命令或者代码块。如果没有任何一个模式匹配,那么就会执行*后面的命令或者代码块。esac是用来结尾的
10、循环
(1)for循环语句
读取不同的变量值,用来逐个执行同一组命令。
- 用法:
读取不同的变量值,用来逐个执行同一组命令
for循环经常使用在已经知道要进行多少次循环的场景
- 格式:
for 变量名 in 取值列表
do
命令序列
done
案例一:循环出0到10之间的整数
法一:
#!/bin/bash
for i in {1..10}
do
echo $i
done
法二:
#!/bin/bash
for i in $(seq 1 10)
do
echo $i
done
输出结果:
案例二:求20以内偶数和
#!/bin/bash
sum=0
for i in {2..20..2}
do
sum=$[sum+i]
let i++
done
echo "20以内的偶数和为:$sum
输出结果:
案例三:根据ip地址列表检查主机状态
#!/bin/bash
IPLIST=$(cat /root/shell/ip.txt)
for IP in $IPLIST
do
ping -c 3 -i 0.5 $IP &> /dev/null
if [ $? -eq 0 ];then
echo "host $IP is up."
else
echo "host $IP is down."
fi
done
输出结果:
(2)while循环
只要条件成立,则反复循环,不成立即停止。
while 条件测试操作
do
命令序列
done
案例一:利用while循环打印一个5x5的*
#!/bin/bash
i=1
j=1
while [ $i -le 5 ]
do
while [ $j -le 5 ]
do
echo -n "* "
let j++
done
echo
let i++
let j=1
done
输出结果:
案例二:while 猜数字小游戏
#!/bin/bash
pc_num=$[RANDOM%3+1]
count=0
while true
do
read -p "请输入一个数字:" user_num
if [ $user_num -eq $pc_num ]
then
echo "答对啦"
break
elif [ $user_num -gt $pc_num ]
then
echo "你的数字太大了"
else
echo "你的数字太小了"
fi
let count++
done
echo 你输入的次数为:$count
输出结果:
(3)until循环语句
重复测试某个条件,只要条件不成立则反复循环。
while 条件测试操作
do
命令序列
done
案例:求50到100的和
#!/bin/bash
sum=0
i=50
until [ $i gt 100]
do
sum=$[sum+i]
let i++
done
echo "{50..100}的和:$sum"
输出结果:
(4)扩充的知识点
break 和 cotinue 命令
11、模块化
- 模块化的原理是在当前shell内执行函数文件
- 方式:source[函数库的路径]
12、常用命令
三、Shell执行过程和原理
1、执行过程
(1)shell脚本一般以.sh结尾,也可以没有,这是一个约定;第一行需要指定用什么命令解释器来执行
#! /bin/bash
复制代码
#! /user/bin/env bash
(2)启动shell的三种方式
#文件名运行(子进程执行)
./filename.sh
#解释器运行(子进程执行)
bash ./filename.sh
# source运行(当前进程中执行)
source ./filename.sh
(3)执行过程
-
字符解析
- 识别换行符、分号(;)做行的分割
- 识别命令连接符(||&&管道)做命令的分割
- 识别空格、tab符,做命令和参数的分割
-
shell展开,例如{1...3}解析为1 2 3
-
重定向,将stdin、stdout、stderr的文件描述符进行指向变更
-
执行命令
- builtin直接执行
- 非builtin使用$PATH查找,然后启动子进程执行
-
收集状态并返回给脚本
2.shell展开
- Shell 展开是指在执行命令之前,Shell 预处理命令行中的各种特殊字符,将它们替换为实际的值或执行相应的操作
(1)大括号展开
- 一般由三部分构成,前缀、一对大括号、后缀,大括号内可以是逗号分割的字符串序列,也可以是序列表达式{x..y[..incr]}
#字符串序列
a(b,c,d)e => abe ace ade
#表达式序列,(数字可以使用incr调整增量,字母不行)
{1..5} => 1 2 3 4 5
{1..5..2} => 1 3 5
{a..e} => a b c d e
(2)波浪号展开
#当前用户主目录
~ => $HOME
~/fo => $HOME/foo
#指定用户的主目录
-fred/foo=>用户fred的SHOEM/foo
#当前工作目录
~+/foo => $PWD/foo
#上一个工作目录
~-/foo => ${$OLDPWD-'~-')/foo
(3)参数展开
- 间接参数扩展${!parameter},其中引用的参数并不是parameter而是parameter的实际的值
parameter="var" # 定义一个名为 parameter 的变量,其值为字符串 "var"
var="hello" # 定义一个名为 var 的变量,其值为字符串 "hello"
echo ${!parameter} # 使用参数展开的形式,将变量 $parameter 的值 "var" 替换为 $var,从而得到变量 $var 的值 "hello",并将其输出到标准输出
#输出 hello
-
参数长度${#parameter}
-
空参数处理
-
S{parameter:-word}#为空替换 -
{parameter:=word#为空替换,并将值赋给parameter变量 -
parameter:?word)#为空报错 -
{parameter:+word}#不为空替换
a=1 # 定义一个名为 a 的变量,其值为整数 1
echo ${a:-word} #1 # 输出变量 $a 的值,如果 $a 未定义或为空,则输出 "word"。由于 $a 已经定义并赋值为 1,因此输出为 1。
echo $(b:-word) #word # 执行命令替换,执行命令 $b 并将其输出作为结果,如果 $b 未定义或为空,则输出 "word"。由于 $b 未定义,因此输出为 "word"。
echo $(par:=word) #word # 执行命令替换,将变量 $par 的值设置为 "word",并将其输出作为结果。由于 $par 未定义,因此输出为 "word"。
echo $(par:-hello) #word # 执行命令替换,输出变量 $par 的值,如果 $par 未定义或为空,则输出 "hello"。由于 $par 未定义,因此输出为 "word"。
echo $(par:+foo) #foo # 执行命令替换,如果 $par 已经定义且非空,则输出 "foo",否则不输出任何内容。由于 $par 未定义,因此不输出任何内容。
#输出分别为:1 word word word foo
复制代码
-
参数切片
{parameter:offset) {parameter:offset:length}
-
参数部分删除
-
{parameter%word} #最小限度从后面截取word -
{parameter%%word}#最大限度从后面截取word -
S{parameter#word}#最小限度从前面截取word -
${parameter##word#最大限度从前面截取word
#!/bin/sh
str=abcdefg # 定义一个名为 str 的变量,其值为字符串 "abcdefg"
spl=$(str##*d) # 执行命令替换,删除变量 $str 开头到最后一个 "d" 之前的所有字符,并将剩余的字符串赋值给变量 $spl。由于变量 $str 的值为 "abcdefg",因此 $spl 的值为 "efg"。
# * 是通配符的一种,表示匹配任意数量的任意字符(包括 0 个字符)。具体来说,*d 表示匹配以字母 “d” 结尾的字符串,并将匹配到的字符串删除。而 ## 表示从变量的开头开始匹配,删除最长的匹配项。
sp2=${str%%d*} # 执行参数展开,删除变量 $str 最后一个 "d" 以及之后的所有字符,并将剩余的字符串赋值给变量 $sp2。由于变量 $str 的值为 "abcdefg",因此 $sp2 的值为 "abc"。
echo $spl #输出efg # 输出变量 $spl 的值,即字符串 "efg"。
echo $sp2 #输出abc # 输出变量 $sp2 的值,即字符串 "abc"。
# echo的两个输出分别如下:efg abc
(4)命令替换
- 在子进程中执行命令,并用得到的结果替换包裹的内容,形式上有两种:$(...)或...
#! /bin/bash
echo ${whoimi} # 输出变量 $whoimi 的值。由于 $whoimi 未定义,因此输出为空。
foo(){ # 定义一个名为 foo 的函数
echo "asdasd" # 在函数中输出字符串 "asdasd"
}
a=`foo` # 执行命令替换,执行函数 foo 并将其输出作为结果,将结果赋值给变量 $a。由于函数 foo 的输出为 "asdasd",因此变量 $a 的值为 "asdasd"。
(5)数学计算
- 使用$( ( ) ) 包裹数学运算表达式,得到结果并替换
#! /bin/bash
echo $((1+2)) # 3
(6)文件名展开
- 当有单词没有被引号包裹,且其中出现了'*','?',and '[' 字符,则shell会去按照正则匹配的方式查找文件名进行替换,如果没找到则保持不变
#! /bin/bash
$ echo D*
# 输出当前目录下所有以 D字母开头的目录、文件
四、调试和前端集成
1、调试
- 普通log,使用echo、printf
- 使用set命令
- vscode debug插件
| set配置 | 作用 | 补充 |
|---|---|---|
| -u | 遇到不存在的变量就会报错,并停止执行。 | -o nounset |
| -x | 运行结果之前,先输出执行的那一行命令。 | -o xtrace |
| -e | 只要发生错误,就终止执行 | -o errexit |
| -o pipefail | 管道符链接的,只要一个子命令失败整个管道命令就失败,脚本就会终止执行。 |
#! /bin/bash
#一般在最前面就进行配置
set -uxe -o pipefail
echo "hello world"
2、Vscode插件配置
-
shellman:代码提示和自动补全
-
shellcheck:代码语法校验
-
shell-format:代码格式化
-
Bash Debug:支持单步调试
- 安装vscode插件
- 编写launch.json文件
- 升级bash到4.x以上版本
3、前端集成
(1)node中通过exec、spawn调用shell命令
(2)shell脚本中调用node命令
(3)借助zx等库进行javascript、shell script的融合
- 借助shell完成系统操作,文件io、内存、磁盘系统状态查看
- 借助nodejs完成应用层能力,网络io、计算等