SHELL

shell脚本

shell

基础

首行

1
2
3
4
5
#!/bin/bash 
#!/usr/bin/python
#!/usr/bin/perl 
#!/usr/bin/ruby
#!/usr/bin/lua
1
2
# 加执行权限
chmod +x test.sh

本地执行远程脚本

1
2
3
4
curl http://10.0.0.157/test.sh -s | bash		# -s 静默
curl http://10.0.0.157/test.sh 2>/dev/null | bash
 
wget -qO -  http://10.0.0.157/test.sh | bash

远程执行本地脚本

1
ssh [email protected] /bin/bash < test2.sh

子shell:(),{}

1
2
3
4
临时shell环境 - 启动子shell
 ( 命令;命令;命令; ),在子shell中执行命令列表,退出子shell后,不影响后续环境操作。
临时shell环境 - 不启动子shell
 { 命令;命令;命令; }, 在当前shell中运行命令列表,会影响当前shell环境的后续操作。左侧要有空格,右侧要有; 

CA创建

1
2
3
4
5
6
7
mkdir /tmp/CA; cd /tmp/CA
# 生成私钥
(umask 077;openssl genrsa -out ca.key 2048)
# 生成证书
openssl req -new -x509 -key ca.key -out ca.crt -days 3650
# 查看证书信息
openssl x509 -in ca.crt -noout -text

脚本实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 # 定制普通环境变量
CA_DIR="tls"
CA_DOMAIN="$1"
CA_KEY='tls.key'
CA_CRT='tls.crt'
 
 # 创建CA证书
mkdir ${CA_DIR}
(umask 077; cd ${CA_DIR}; openssl genrsa -out tls.key 2048)
openssl req -new -x509 -key ${CA_DIR}/${CA_KEY} -out ${CA_DIR}/${CA_CRT} -subj "/CN=${CA_DOMAIN}" -days 365
1
/bin/bash ca_create.sh www.example.com

开写

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
lscpu | sed -nr 's/^Model name: +(.*)/\1/p'
cat /proc/meminfo | head -n 1 | tr -s " " | cut -d" " -f2,3
lsblk /dev/sda | grep "^sda" | tr -s " " | cut -d" " -f4 
cat /etc/os-release | sed -nr "s/^VERSION=\"(.*)\"/\1/p"


cho -e "CPU    \c" 
lscpu | sed -nr 's/^Model name: +(.*)/\1/p'
echo -e "Mem    \c" 
cat /proc/meminfo | head -n 1 | tr -s " " | cut -d" " -f2,3
echo -e "DISK   \c" 
lsblk /dev/sda | grep "^sda" | tr -s " " | cut -d" " -f4 
echo -e "OS		\c" 
cat /etc/os-release | sed -nr "s/^VERSION=\"(.*)\"/\1/p"



cho -e "========================= sysinfo begin ================\n"
echo -e "CPU    \c" 
echo -e "\E[1;32m `lscpu | sed -nr 's/^Model name: +(.*)/\1/p'` \E[0m"
echo -e "Mem    \c" 
echo -e "\E[1;32m `cat /proc/meminfo | head -n 1 | tr -s " " | cut -d" " -f2,3` \E[0m"
echo -e "OS      \c" 
echo -e "DISK   \c" 
echo -e "\E[1;32m `lsblk /dev/sda | grep "^sda" | tr -s " " | cut -d" " -f4` \E[0m"
echo -e "\E[1;32m `cat /etc/os-release | sed -nr 's/^VERSION=\"(.*)\"/\1/p'` \E[0m"
echo -e "\n========================= sysinfo end =================="
1
2
bash -n test.sh 	# 语法检查
bash -x test.sh 	# 调试执行

变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
PS1,PATH,UID,HOSTNAME,$$,BASHPID,PPID,$?,HISTSIZE

PPID 				# 前进程的父进程 ID。
$$ 					# 当前 shell 进程的 PID
$? 					# 上一个命令的退出状态码。0 表示成功,非 0 表示失败。
$!					# 前一个命令的PID,方便杀死
$_					# 表示上输入的一个参数.当这个命令在开头时, 打印输出文档的绝对路径名.
$-					# 显示当前 shell 的选项标志。
$1,$2,.......		# 第几个参数
$0					# 脚本文件名,包括路径
$*					# 所有参数,一个字符串
$@					# 所有参数,每个参数独立字符
$#					# 参数的个数

变量命名:只能使用数字、字母及下划线,且不能以数字开头

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
name='root'             #直接字串       
name="$USER"            #变量引用        
name=`COMMAND`          #命令引用        
name=$(COMMAND)         #命令引用 

"$name" # 弱引用,其中的变量引用会被替换为变量值
'$name' # 强引用,其中的变量引用不会被替换为变量值,而保持原字符串

seq 5
1
2
3
4
5
NUM=`seq 5`
echo "$NUM"
1
2
3
4
5

echo $NUM
1 2 3 4 5
# ""会保留原格式

# 使用${}给变量圈定范围
echo ${NAME}_$AGE
# 查找全局变量F
env | grep SHELL

# 变量追加
TITLE=CTO
TITLE+=:wang
echo $TITLE
CTO:wang

#set 命令会列出所有已定义的 shell 变量和环境变量,以及它们的当前值。输出内容包括用户定义的变量、系统变量和函数。
set
# 删除变量
unset <name>

# 环境变量
export name=VALUE
# 只读变量,不可删除
readonly name=VALUE

# 位置变量
$1,$2,.......		# 第几个参数
$0					# 命令本身,包括路径
$*					# 所有参数,一个字符串
$@					# 所有参数,每个参数独立字符
$#					# 参数的个数

# 自定义退出码
exit [n]

变量默认值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# ${变量名:-默认值}
# 有参就为参,无参就为1
a="$1"
echo ${a:-1}

# 强制默认值,无论a是什么,a都为1
${a+1}

${VAR:?error_message}
# 解释:如果 VAR 未定义或为空,则显示 error_message 并中止脚本。

设定变量默认值,无参数传来就为默认

image-20241101180705710

展开命令行

  1. 把命令行分成单个命令词
  2. 展开别名
  3. 展开大括号的声明{}
  4. 展开波浪符声明 ~
  5. 命令替换$() 和 ``
  6. 再次把命令行分成命令词
  7. 展开文件通配符*、?、[abc]等等
  8. 准备I/0重导向 <、>
  9. 运行命令

$-

image-20241101173001125

1
2
set -x  # 启用命令跟踪
set +x  # 禁用命令跟踪

image-20241101173129470

字符串

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
str=abcdefg

${#str}			# 返回字符串长度
4

# 字符串以0开始
# 字符串截取
${str:0:5}		# 从0开始,截取5个
${str:5:5}		# 从第六个开始,截取5个
${str::5}		# 从0开始,截取5个
${str:0-6:5}	# 从倒数第6开始,向后截取5个
${str: -4}		# 字符串最后四个

printf

1
printf "%d%s" a b

image-20241101174808450

算术运算:$[],$(()),(())

bash 只支持整数,不支持小数

let var=expression

var=$[expression]

var=$((expression))

((var=expression))

declare -i var=number

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
i=123;j=456;
k=i+j;echo $k
i+j

let k=i+j;echo $k
579

expr 10+20
10+20

expr 10 + 20
30

expr 10 \* 20 # *需要转义
200

let num1+=1

((num1+=1))

expr

image-20241101195603140

bc:除法小数实现

1
2
3
4
5
-l		# 实现小数数学运算
-q		# 静默

echo "scale=3;20/3"|bc
echo "20/3"|bc -l

随机变量

1
$RANDOM                                 #取值范围:0-32767

^:异或

同为假

异为真

$()

字符串操作

image-20241104103418415

默认值相关

image-20241104103453847

expr

image-20241104103154482

read

1
2
3
4
5
6
read -p "请输入登录用户名: " user
# 限制字符数
read -n 6 -p "sss: " aaa
# 时长限制
read -t 5 -p "等待5秒后自动退出! " second
-d 'q'		# 结束符设定

|:管道通常会创建子 Shell,因此在管道中定义的变量在当前 Shell 中不可见。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
read i j < test.txt ; echo $i $j
1 2

echo 1 2 | read x y ; echo $x $y
无输出

echo 1 2 | ( read x y ; echo $x $y )
1 2

echo 1 2 | { read x y ; echo $x $y; }
1 2

条件测试命令

判断某需求是否满足

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
test xxxx

[ xxxxx ]

[[ xxxxx ]]		# 判断字符串是否匹配;==(通配符);=~(正则)

# 状态码
0 为true
1 为false

# -a 和 -o 需要使用测试命令进行,[[ ]] 不支持
[ EXPRESSION1 -a EXPRESSION2 ]		# and
[ EXPRESSION1 -o EXPRESSION2 ]		# or
[ ! EXPRESSION1 ]					# 取反

文件判断

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-a FILE			# 判断文件是否存在
-e FILE			# 判断文件是否存在
-s FILE			# 判断文件是否存在且不为空

-r FILE			# 判断文件当前用户是否可读
-w FILE			# 判断文件当前用户是否可写
-x FILE			# 判断文件当前用户是否可执行
-O FILE			# 判断文件当前用户是否为所属人
-G FILE			# 判断文件当前用户是否为所属组

-k FILE			# 判断文件是否有sticky位
-u FILE			# 判断文件是否有SUID位

-b FILE			# 判断文件是否为块设备
-c FILE			# 判断文件是否字符设备
-d FILE			# 判断文件是否为目录
-f FILE			# 判断文件是否为普通文件
-p FILE			# 判断文件是否为管道
-S FILE			# 判断文件是否为套接字
-h FILE			# 判断文件是否为软链接
-L FILE			# 判断文件是否为软链接
FILE1 -eq FILE2 	# 判断文件1是否为新于文件2的硬链接

-t FD			# 判断文件描述符是否指向一个终端;判断脚本的输入是否来自于用户的终端

FILE1 -nt FILE2		# 判断文件1是否新于文件2
FILE1 -ot FILE2		# 判断文件1是否旧于文件2

字符串判断

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
-z STRING			# 空为真
-n STRING			# 非空为真
STRING

STRING1 = STRING2
STRING1 != STRING2

==                                  
!=                                  
<=                                  
>=                                  
<                                   
>

数学判断;$(())与(())

  • $(())用于求值返回结果
  • (())用于求值返回布尔结果
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 用于整数的比较
-eq
-ne
-lt
-le
-gt
-ge

a=5
b=10

# $(())用于求值返回结果
# (())用于求值返回布尔结果

if (( a >= b )); then
    echo "a 大于等于 b"
fi

# 可以嵌入在条件判断中
if (( $(($a + $b)) > 10 )); then
    echo "a + b 大于 10"
fi

# 小数比较
a=5.5
b=10.2

# 使用 bc -l 比较小数
if (( $(echo "$a < $b" | bc -l) )); then
    echo "$a 小于 $b"
fi

其他

1
-v VAR		# 是否已赋值

test:IP

1
2
3
4
[[ $IP =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]]
 
IP=$1
[[ $IP =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]] && echo $IP is valid || echo $IP is invalid

&&,|| 与组合使用

&&|| 可以组合使用,形成类似“if…else”的逻辑结构。可以用以下方式来表达“如果命令成功则执行 A,否则执行 B”:

1
2
3
4
5
command && echo "Success" || echo "Failure"

COMMAND1 && COMMAND2      # 1成功,2执行; 1失败,2不执行                             
COMMAND1 || COMMAND2      # 1成功,2不执行; 1失败,2执行                
! COMMAND            	  #非,取反                                   

image-20241102100534348

true,false;没有0,1

if…fi

1
2
3
4
5
6
7
8
9
if commands
then
	xxxxxx
elif commands
then
	xxxxxx
else
	xxxxxx
fi

case…esac

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
case $input in
case1)
	xxxx
	xxxx
	;;
case2)
	xxxx
	xxxx
	;;
case3)
	xxxx
	;;
*)	# 都没匹配到;default
	xxxx
	;;
esac
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
read -p "Do you agree(yes/no)? " INPUT
case $INPUT in
[yY]|[Yy][Ee][Ss])
	echo "You input is YES"
	;;  
[Nn]|[Nn][Oo])
 	echo "You input is NO"
    ;;  
*)
	echo "Input fales,please input yes or no!"      
    ;;  
esac

for…done;(())

双小括号方法,即((…))格式,也可以用于算术运算,双小括号方法也可以使bash Shell实现C语言风格的变量操作.

1
2
3
4
for i in item1 item2 ... itemN
do
	xxxx
done
1
2
3
4
5
6
for((i=1;i<=9;i++));do
	for((j=1;j<=i;j++));do
		printf "\E[1;$[RANDOM%7+31]m${i}x${j}=$[i*j]\E[0m\t"
	done
	printf "\n"
done

while…done

1
2
3
4
while condition
do
	xxxx
done

image-20241102104919945

: 是 Shell 中的一个内建命令,它也是一个始终返回成功状态的空操作符。

while read

1
2
3
4
while read line
do
	循环体
done < /PATH/FROM/SOMEFILE

unitl

1
2
3
4
until condition
do
    command
done

continue

break

shift

shift [n] 用于将参量列表 list 左移指定次数,缺省为左移一次。

参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到 shift

image-20241102105404199

select

image-20241102110444849

image-20241102110521834

function;自定义函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
function func_name(){

}

func_name(){

}

function func_name{

}

# 调用,直接函数名
func_name

# 引入函数文件
source func_file
. func_file

# 返回值
return 0
return xxx
# 获取返回值
使用 $?

# 参数
func_name 1 2 3 4
$1, $2, ...调用这些参数;还可以使用$@, $*, $#等特殊变量

# 本地变量
# 默认全是全局
# 使用local为本地变量;变量会被限定在函数作用域内,函数结束后就无法在外部访问它。
my_function() {
    var1="Hello from function"
}

my_function
echo "$var1"  # 输出: Hello from function
#***************************************#
my_function() {
    local var1="Hello from function"
}

my_function
echo "$var1"  # 输出为空,或者会提示未定义

环境函数

使子进程也可使用父进程定义的函数

1
2
export -f function_name 
declare -xf function_name