2

shell编程语法

 2 years ago
source link: https://blog.51cto.com/taowenwu/5252248
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

shell编程语法

原创

小怪獣55 2022-04-25 10:02:52 博主文章分类:shell编程 ©著作权

文章标签 shell编程语法 文章分类 Linux 系统/运维

1.shell编程

shell编程:过程式.解释执行
#编程语言的基本结构:
各种系统命令的组合
数据存储:变量.数组
表达式:a + b
语句:if

2.shell脚本

shell脚本:
包含一些命令或声明,并符合一定格式的文本文件
格式要求:首行shebang机制
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl

#shell脚本的用途有:
自动化常用命令
执行系统管理和故障排除
创建简单的应用程序
处理文本或文件

3.创建shell脚本

#第一步:使用文本编辑器来创建文本文件
第一行必须包括shell声明序列:#!
示例:#!/bin/bash
添加注释
注释以#开头
#第二步:运行脚本
给予执行权限,在命令行上指定脚本的绝对或相对路径
直接运行解释器,将脚本作为解释器程序的参数运行

4.脚本规范

脚本代码开头约定
1.第一行一般为调用使用的语言
2.程序名,避免更改文件名为无法找到正确的文件
3.版本号
4.更改后的时间
5.作者相关信息
6.该程序的作用,及注意事项
7.最后是各版本的更新简要说明

#脚本的基本结构
#!SHEBANG
CONFIGURATION_VARIABLES
FUNCTION_DEFINITIONS
MAIN_CODE

#shell脚本示例
#!/bin/bash
# ------------------------------------------
# Filename: hello.sh
# Revision: 1.1
# Date: 2017/06/01
# Author: test
# Email: [email protected]
# Website: www.test.com
# Description: This is the first script
# Copyright: 2017 test
# License: GPL
# ------------------------------------------
echo "hello world"

5.脚本调试

#检测脚本中的语法错误
bash -n /path/to/some_script
#调试执行
bash -x /path/to/some_script
变量:命名的内存空间
#作用:
1.数据存储方式
2.参与的运算
3.表示的数据范围

#类型:
字符
数值:整型/浮点型

静态编译语言:使用变量前,先声明变量类型,之后类型不能改变,在编译时检查,如:java,c
动态编译语言:不用事先声明,可随时改变类型,如bash,Python

强类型语言:不同类型数据操作,必须经过强制转换,同一类型才能运算,如java , c# ,python
如:以下python代码
print('test'+ 10) 提示出错,不会自动转换类型
print('test'+str(10)) 结果为test10,需要显示转换类型
弱类型语言:语言的运行时会隐式做数据类型转换。无须指定类型,默认均为字符型;参与运算会自动进行隐式类型转换;变量无须事先定义可直接调用
如:bash 不支持浮点数,php,javascript

6.1.Shell中变量

#Shell中变量命名法则:
1.不能使程序中的保留字:例如if, for
2.只能使用数字.字母及下划线,且不能以数字开头
3.见名知义
4.统一命名规则:驼峰命名法(大驼峰:第一个字母大写,后边也大写;小驼峰:第一个字母小写,后边大写)
#Shell中命名建议规则:
1.变量名大写
2.局部变量小写
3.函数名小写
4.用英文名字,并体现出实际作用

6.2.变量的种类

根据变量的生效范围等标准划分下面变量类型
1)局部变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效
2)环境变量:生效范围为当前shell进程及其子进程
3)本地变量:生效范围为当前shell进程中某代码片断,通常指函数
4)位置变量:$1, $2, ...来表示,用于让脚本在脚本代码中调用通过命令行传递给它的参数
5)特殊变量:$?, $0, $*, $@, $#,$$
$- 显示shell使用的当前选项,与set命令功能相同
$# 添加到Shell的参数个数
$0 Shell本身的文件名
$* 所有参数列表; "$*" 以"$1 $2 … $n"的形式输出所有参数
$@ 所有参数列表; "$@" 以"$1" "$2" … "$n" 的形式输出所有参数
$? 最后运行的命令的结束代码; 0表示没有错误,其他任何值表明有错误
$! Shell最后运行的后台Process的PID
$$ Shell本身的PID

6.2.1.局部变量

变量赋值:name='value'
可以使用引用value
(1) 可以是直接字串:name="root"
(2) 变量引用:name="$USER"
(3) 命令引用:name=`COMMAND`
name=$(COMMAND)

变量引用:${name} 或者 $name
" " 弱引用,其中的变量引用会被替换为变量值
' ' 强引用,其中的变量引用不会被替换为变量值,而保持原字符串
显示已定义的所有变量:set
删除变量:unset name

6.2.2.环境变量

#变量声明、赋值:
export name=VALUE
declare -x name=VALUE

#变量引用:
$name, ${name}

#显示所有环境变量:
env
printenv
export
declare -x

#删除变量:
unset name

#bash内建的环境变量
PATH
SHELL
USER
UID
HOME
PWD
SHLVL
LANG
MAIL
HOSTNAME
HISTSIZE
_ 下划线

6.2.3.位置变量

位置变量:在脚本代码中调用通过命令行传递给脚本的参数
$1, $2, ... 对应第1、第2等参数,shift [n]换位置

set -- 清空所有位置变量

6.2.4.只读变量

只读变量:只能声明,但不能修改和删除
声明只读变量:
readonly name
declare -r name
查看只读变量:
readonly -p

7.退出状态

进程使用退出状态来报告成功或失败
0 代表成功,1-255代表失败
$? 变量保存最近的命令退出状态
例如:
ping -c1 -W1 hostdown &> /dev/null
echo $?

bash自定义退出状态码
exit [n]:自定义退出状态码
注意:脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
注意:如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码

8.表达式

8.1.算术运算

bash中的算术运算:help let
+, -, *, /, %取模(取余), **(乘方),乘法符号有些场景中需要转义
实现算术运算:
(1) let var=算术表达式
(2) var=$[算术表达式]
(3) var=$((算术表达式)) #在 $(( )) 中的变量名称,可于其前面加 $ 符号来替换,也可以不用
(4) var=$(expr arg1 arg2 arg3 ...)
(5) declare –i var = 数值
(6) echo '算术表达式' | bc
bash有内建的随机数生成器变量:$RANDOM(0-32767)
示例:生成 0 - 49 之间随机数
echo $[$RANDOM%50]
$(( ))可以将其他进制转成十进制数显示出来。用法如下:
echo $((N#xx))
其中,N为进制,xx为该进制下某个数值,命令执行后可以得到该进制数转成十进制后的值
echo $((2#110)) # 二进制转十进制
6
echo $((16#2a)) #十六进制转十进制
42
echo $((8#11)) #八进制转十进制
9

8.2.赋值运算

增强型赋值:
+=, -=, *=, /=, %=
#let varOPERvalue
例如:let count+=3
自加3后自赋值
#自增,自减:
let var+=1
let var++
let var-=1
let var--

8.3.逻辑运算

true(1), false(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 ! true
! 0 = 1 ! false

#短路运算
#短路与
第一个为0,结果必定为0
第一个为1,第二个必须要参与运算
#短路或
第一个为1,结果必定为1
第一个为0,第二个必须要参与运算
#异或:^
异或的两个值,相同为假,不同为真
1^1=0
0^0=0
1^0=1
0^1=1

8.4.条件测试

判断某需求是否满足,需要由测试机制来实现
专用的测试表达式需要由测试命令辅助完成测试过程
评估布尔声明,以便用在条件性执行中
• 若真,则返回0
• 若假,则返回1
#测试命令:
test EXPRESSION
[ EXPRESSION ]
[[ EXPRESSION ]]

 注意:EXPRESSION前后必须有空白字符

8.4.1.数值测试

-v VAR 变量VAR是否设置
#数值测试:
-gt 是否大于 #greater than
-ge 是否大于等于

-eq 是否等于
-ne 是否不等于

-lt 是否小于 #less than
-le 是否小于等于

8.4.2.字符串测试

#字符串测试:
= 是否等于
> ascii码是否大于ascii码
< 是否小于
!= 是否不等于
=~ 左侧字符串是否能够被右侧的PATTERN所匹配
#注意: 此表达式一般用于[[ ]]中;扩展的正则表达式

-z "STRING" 字符串是否为空,空为真,不空为假
-n "STRING" 字符串是否不空,不空为真,空为假
#注意:用于字符串比较时用到的操作数都应该使用引号

8.4.3.文件存在测试

#存在性测试
-a FILE: 同 -e
-e FILE: 文件存在性测试,存在为真,否则为假

#存在性及类别测试
-b FILE:是否存在且为块设备文件
-c FILE:是否存在且为字符设备文件
-d FILE:是否存在且为目录文件
-f FILE:是否存在且为普通文件
-h FILE 或 -L FILE:存在且为符号链接文件
-p FILE:是否存在且为命名管道文件
-S FILE:是否存在且为套接字文件

8.4.4.文件权限测试

#文件权限测试:
-r FILE:是否存在且可读
-w FILE: 是否存在且可写
-x FILE: 是否存在且可执行

#文件特殊权限测试:
-u FILE:是否存在且拥有suid权限
-g FILE:是否存在且拥有sgid权限
-k FILE:是否存在且拥有sticky权限

8.4.5.文件属性测试

#文件大小测试:
-s FILE: 是否存在且非空

#文件是否打开:
-t fd: fd文件描述符是否在某终端已经打开
-N FILE: 文件自从上一次被读取之后是否被修改过
-O FILE: 当前有效用户是否为文件属主
-G FILE: 当前有效用户是否为文件属组

#双目测试:
FILE1 -ef FILE2: FILE1是否是FILE2的硬链接
FILE1 -nt FILE2: FILE1是否新于FILE2(mtime)
FILE1 -ot FILE2: FILE1是否旧于FILE2

9.组合测试条件

#第一种方式:
EXPRESSION1 -a EXPRESSION2 并且
EXPRESSION1 -o EXPRESSION2 或者
! EXPRESSION
必须使用测试命令进行,[[ ]] 不支持

#第二种方式:
COMMAND1 && COMMAND2 并且,短路与,代表条件性的AND THEN
COMMAND1 || COMMAND2 或者,短路或,代表条件性的OR ELSE
! COMMAND 非
如:[ -f "$FILE" ] && [[ "$FILE"=~ .*\.sh$ ]]

#示例:
test "$A" = "$B" && echo "Strings are equal"
test "$A"-eq "$B" && echo "Integers are equal"
[ "$A" = "$B" ] && echo "Strings are equal"
[ "$A" -eq "$B" ] && echo "Integers are equal"
[ -f /bin/cat -a -x /bin/cat ] && cat /etc/fstab
[ -z "$HOSTNAME" -o $HOSTNAME =="localhost.localdomain" ] && hostname www.magedu.com

10.()与{}区别

#小括号()
执行一串命令时,需要重新开启一个子 Shell 来执行。
最后一条命令可以不用分号
里的各命令不必和括号有空格
#大括号{}
执行一串命令时,在当前 Shell 中执行。
最后一条命令要用分号
第一条命令和左括号之间必须有一个空格


() 和 {} 都是把一串命令放括号里面,并且命令之间用";"隔开。
() 和 {} 中括号里面的某条命令的重定向只影响该命令,但括号外的重定向则会影响到括号里的所有命令。


[root@CentOS7 ~]# name=tao
[root@CentOS7 ~]# echo $name
tao
[root@CentOS7 ~]# (name=ww;echo $name)
ww
[root@CentOS7 ~]# echo $name
tao
[root@CentOS7 ~]# { name=yy;echo $name;}
yy
[root@CentOS7 ~]# echo $name
yy

11.使用read命令来接受输入

使用read来把输入值分配给一个或多个shell变量
-p 指定要显示的提示
-s 静默输入,一般用于密码
-n N 指定输入的字符长度N
-d '字符' 输入结束符
-t N TIMEOUT为N秒
read 从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量
read -p "Enter a filename: " FILE

12.条件选择if语句

选择执行:
注意:if语句可嵌套
#单分支
if 判断条件;then
条件为真的分支代码
fi

#双分支
if 判断条件; then
条件为真的分支代码
else
条件为假的分支代码
fi

#多分支
if 判断条件1; then
条件1为真的分支代码
elif 判断条件2; then
条件2为真的分支代码
elif 判断条件3; then
条件3为真的分支代码
else
以上条件都为假的分支代码
fi
逐条件进行判断,第一次遇为"真"条件时,执行其分支,而后结束整个if语句

13.条件判断:case语句

case 变量引用 in
PAT1)
分支1
;;
PAT2)
分支2
;;
...
*)
默认分支
;;
esac

case支持glob风格的通配符
*: 任意长度任意字符
?: 任意单个字符
[]:指定范围内的任意单个字符
a|b: a或b

13.bash如何展开命令行

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

14.防止扩展

#反斜线(\)会使随后的字符按原意解释
echo Your cost: \$5.00
Your cost: $5.00

#加引号来防止扩展
• 单引号('')防止所有扩展
• 双引号("")也可防止扩展,但是以下情况例外:
$(美元符号) 变量扩展
` ` (反引号) 命令替换
\(反斜线) 禁止单个字符扩展
!(叹号) 历史命令替换

15.bash的配置文件

按生效范围划分,存在两类:
#全局配置:
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
#个人配置:
~/.bash_profile
~/.bashrc

16.shell登录两种方式

#交互式登录:
(1)直接通过终端输入账号密码登录
(2)使用"su - UserName" 切换的用户
执行顺序:/etc/profile --> /etc/profile.d/*.sh --> ~/.bash_profile --> ~/.bashrc --> /etc/bashrc

#非交互式登录:
(1)su UserName
(2)图形界面下打开的终端
(3)执行脚本
(4)任何其它的bash实例
执行顺序: /etc/profile.d/*.sh --> /etc/bashrc -->~/.bashrc

Profile类 按功能划分,存在两类:
-------------------------------------
profile类和bashrc类
#profile类:为交互式登录的shell提供配置
全局:/etc/profile, /etc/profile.d/*.sh
个人:~/.bash_profile
功用:
(1) 用于定义环境变量
(2) 运行命令或脚本

#Bashrc类 bashrc类:为非交互式和交互式登录的shell提供配置
全局:/etc/bashrc
个人:~/.bashrc
功用:
(1) 定义命令别名和函数
(2) 定义本地变量

修改profile和bashrc文件后需生效
两种方法: 1重新启动shell进程
2 . 或source
例:
. ~/.bashrc

#Bash 退出任务
保存在~/.bash_logout文件中(用户)
在退出登录shell时运行
用于
• 创建自动备份
• 清除临时文件

17.for 循环

for是每次读取文件中一个以空格为分隔符的字符串

for 变量名 in 列表;do
循环体
done

#执行机制:
依次将列表中的元素赋值给"变量名"; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束
#列表生成方式:
(1) 直接给出列表
(2) 整数列表:
(a) {start..end}
(b) $(seq [start [step]] end)
(3) 返回列表的命令
$(COMMAND)
(4) 使用glob,如:*.sh
(5) 变量引用
$@, $*

seq -s+100 |bc
tr -dc '[:alnum:]' < /dev/urandom |head -c 8


#for特殊格式
双小括号方法,即((…))格式,也可以用于算术运算
双小括号方法也可以使bash Shell实现C语言风格的变量操作
I=10
((I++))
---------------------
#for循环的特殊格式:
for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do
循环体
done
控制变量初始化:仅在运行到循环代码段时执行一次
控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断

for i in {1..10} ;do
useradd user$i
passwd=`tr -dc '[:alnum:]' < /dev/urandom |head -c 8`
echo user$i:$password |tee -a pass.txt|chpasswd
passwd -e user$i #设置密码过期
echo "user:user$i is created"
done

18.while循环

while CONDITION; do
循环体
done
CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为"true",则执行一次循环;直到条件测试状态为"false"
终止循环
因此:CONDTION一般应该有循环控制变量;而此变量的值会在循环体不断地被修正
进入条件:CONDITION为true
退出条件:CONDITION为false


while read line 是一次性将文件信息读入并赋值给变量line
while read line; do
循环体
done < /PATH/FROM/SOMEFILE
依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line

19.until循环

until CONDITION; do
循环体
done
进入条件: CONDITION 为false
退出条件: CONDITION 为true

until 与while 命令工作的方式完全相反

20.循环控制语句

用于循环体中

#continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层
while CONDTIITON1; do
CMD1
...
if CONDITION2; then
continue
fi
CMDn
...
done


#break [N]:提前结束第N层循环,最内层为第1层
while CONDTIITON1; do
CMD1
...
if CONDITION2; then
break
fi
CMDn
...
done

21.循环控制shift命令

shift [n]
用于将参量列表 list 左移指定次数,缺省为左移一次。
参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到 shift
./doit.sh a b c d e f g h
./shfit.sh a b c d e f g h

while [ $# -gt 0 ] # or (( $# > 0 )) do
echo $*
shift
done

[root@lvs data]#bash shift.sh a b c d e f g h
a b c d e f g h
b c d e f g h
c d e f g h
d e f g h
e f g h
f g h
g h
h


until [ -z "$1" ]
do
echo "$1"
shift
done
[root@lvs data]#bash shift.sh a b c d e f g h
a
b
c
d
e
f
g
h

22.创建无限循环

while true; do
循环体
done

until false; do
循环体
Done

23.select循环与菜单

select variable in list
do
循环体命令
done
select 循环主要用于创建菜单,按数字顺序排列的菜单项将显示在标准错误上,并显示 PS3 提示符,等待用户输入
用户输入菜单列表中的某个数字,执行相应的命令
用户输入被保存在内置变量 REPLY 中 select 是个无限循环,因此要记住用 break 命令退出循环,或用 exit 命令终止
终止脚本。也可以按 ctrl+c 退出循环
select 经常和 case 联合使用
与 for 循环类似,可以省略 in list,此时使用位置参量


PS3="please input a number :"
select menu in gongbaojiding kaoya foutiaoqiang haishen baoyu quit ;do
case $REPLY in
1)
echo $menu price is ¥30
;;
2)
echo $menu price is ¥80
;;
3)
echo $menu price is ¥200
;;
4)
echo $menu price is \$10
;;
5)
echo $menu price is \$50
;;
6)
break
;;
*)
echo "please input again"
esac
done

24.函数

函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程
它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分
#函数和shell程序比较相似,区别在于
Shell程序在子Shell中运行
而Shell函数在当前Shell中运行。因此在当前Shell中,函数可以对shell中变量进行修改

#函数由两部分组成:函数名和函数体
help function
#语法一:
f_name (){
...函数体...
}
#语法二:
function f_name {
...函数体...
}
#语法三:
function f_name () {
...函数体...
}

#函数的定义和使用:
可在交互式环境下定义函数
可将函数放在脚本文件中作为它的一部分
可放在只包含函数的单独文件中

调用:函数只有被调用才会执行
调用:给定函数名
函数名出现的地方,会被自动替换为函数代码
函数的生命周期:被调用时创建,返回时终止

#函数有两种返回值:
函数的执行结果返回值:
(1) 使用echo等命令进行输出
(2) 函数体中调用命令的输出结果

#函数的退出状态码:
(1) 默认取决于函数中执行的最后一条命令的退出状态码
(2) 自定义退出状态码,其格式为:
return 从函数中返回,用最后状态命令决定返回值
return 0 无错误返回
return 1-255 有错误返回

#交互式环境下定义和使用函数
示例:
[root@openstick1 ~]#dir(){
> ls -l
> }

[root@openstick1 ~]#dir
total 16
-rw-r--r-- 1 root root 3 Apr 24 19:59 1.txt
-rw-------. 1 root root 1415 Oct 2 2021 anaconda-ks.cfg
-rw-r--r-- 1 root root 3 Apr 24 19:36 t.txt
-rw-r--r-- 1 root root 1 Apr 25 08:17 var.sh

该dir函数将一直保留到用户从系统退出,或执行了如下所示的unset命令
unset dir

#在脚本中定义及使用函数
函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至shell首次发现它后才能使用
调用函数仅使用其函数名即可
示例:
cat func1
#!/bin/bash
# func1
hello()
{
echo "Hello there today's date is `date +%F`"
}
echo "now going to the function hello"
hello
echo "back from the function"

#使用函数文件
可以将经常使用的函数存入函数文件,然后将函数文件载入shell
文件名可任意选取,但最好与相关任务有某种联系。例如:functions.main
一旦函数文件载入shell,就可以在命令行或脚本中调用函数。
可以使用set命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数
若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件

24.1.函数文件

函数文件示例:
cat functions.main
#!/bin/bash
#functions.main
findit()
{
if [ $# -lt 1 ] ; then
echo "Usage:findit file"
return 1
fi
find / -name $1 -print
}

#载入函数
函数文件已创建好后,要将它载入shell
定位函数文件并载入shell的格式
. filename 或 source filename
注意:此即<点> <空格> <文件名>
这里的文件名要带正确路径
示例:
上例中的函数,可使用如下命令
. functions.main

#检查载入函数
使用set命令检查函数是否已载入。set命令将在shell中显示所有的载入函数
示例:
set
findit=( )
{
if [ $# -lt 1 ]; then
echo "usage :findit file";
return 1
fi
find / -name $1 -print
}


#执行shell函数
要执行函数,简单地键入函数名即可
示例:
findit groups
/usr/bin/groups
/usr/local/backups/groups.bak

#删除shell函数
现在对函数做一些改动后,需要先删除函数,使其对shell不可用。使用unset命令完成删除函数
命令格式为:
unset function_name
示例:
unset findit
再键入set命令,函数将不再显示

#环境函数
使子进程也可使用
声明:export -f function_name
查看:export -f 或 declare -xf

#函数参数
函数可以接受参数:
传递参数给函数:调用函数时,在函数名后面以空白分隔给定参数列表即可;
例如"testfunc arg1 arg2 ..."
在函数体中当中,可使用$1, $2, ...调用这些参数;还可以使用$@, $*, $#等特殊变量

#函数变量
变量作用域:
环境变量:当前shell和子shell有效
本地变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
局部变量:函数的生命周期;函数结束时变量被自动销毁
注意:如果函数中有局部变量,如果其名称同本地变量,使用局部变量
在函数中定义局部变量的方法
local NAME=VALUE

#函数递归示例
函数递归:
函数直接或间接调用自身
注意递归层数

递归实例:
阶乘是基斯顿·卡曼于 1808 年发明的运算符号,是数学术语,一个正整数的阶
乘(factorial)是所有小于及等于该数的正整数的积,并且有0的阶乘为1,自然
数n的阶乘写作n!
n!=1×2×3×...×n
阶乘亦可以递归方式定义:0!=1,n!=(n-1)!×n
n!=n(n-1)(n-2)...1
n(n-1)! = n(n-1)(n-2)!

#函数递归示例
示例:fact.sh
#!/bin/bash
#
fact() {
if [ $1 -eq 0 -o $1 -eq 1 ]; then
echo 1
else
echo $[$1*$(fact $[$1-1])]
fi
}
fact $1

25.fork炸弹

fork炸弹是一种恶意程序,它的内部是一个不断在fork进程的无限循环,实质是一个简单的递归程序。由于程序是
递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源
#函数实现
:(){ :|:& };:
bomb() { bomb | bomb & }; bomb

#脚本实现
cat Bomb.sh
#!/bin/bash
./$0|./$0&

26.信号捕捉trap

trap '触发指令' 信号
进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作
trap '' 信号
忽略信号的操作
trap '-' 信号
恢复原信号的操作
trap -p
列出自定义信号操作
trap finish EXIT
当脚本退出时,执行finish函数

#trap示例
#!/bin/bash
trap 'echo "signal:SIGINT"' int
trap -p
for((i=0;i<=10;i++))
do
sleep 1
echo $i
done
trap '' int
trap -p
for((i=11;i<=20;i++))
do
sleep 1
echo $i
done
trap '-' int
trap -p
for((i=21;i<=30;i++))
do
sleep 1
echo $i
done

27.数组

变量:存储单个元素的内存空间
数组:存储多个元素的连续的内存空间,相当于多个变量的集合
#数组名和索引
索引:编号从0开始,属于数值索引
注意:索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0版本之后开始支持
bash的数组支持稀疏格式(索引不连续)
#声明数组:
declare -a ARRAY_NAME
declare -A ARRAY_NAME 关联数组
注意:两者不可相互转换


#数组元素的赋值
(1) 一次只赋值一个元素
ARRAY_NAME[INDEX]=VALUE
weekdays[0]="Sunday"
weekdays[4]="Thursday"
(2) 一次赋值全部元素
ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)
(3) 只赋值特定元素
ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)
(4) 交互式数组值对赋值
read -a ARRAY
显示所有数组:declare -a


#引用数组元素
${ARRAY_NAME[INDEX]}
注意:省略[INDEX]表示引用下标为0的元素
引用数组所有元素
${ARRAY_NAME[*]}
${ARRAY_NAME[@]}
数组的长度(数组中元素的个数)
${#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}
删除数组中的某元素:导致稀疏格式
unset ARRAY[INDEX]
删除整个数组
unset ARRAY


#引用数组中的元素:
数组切片:
${ARRAY[@]:offset:number}
offset 要跳过的元素个数
number 要取出的元素个数

取偏移量之后的所有元素
${ARRAY[@]:offset}

向数组中追加元素:
ARRAY[${#ARRAY[*]}]=value

关联数组:
declare -A ARRAY_NAME
ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2'...)
注意:关联数组必须先声明再调用


root@ubuntu:~# ALPHA=({A..Z})
root@ubuntu:~# echo $ALPHA
A
root@ubuntu:~# echo ${ALPHA[@]}
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
root@ubuntu:~# echo ${ALPHA[*]}
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
root@ubuntu:~# echo ${ALPHA[0]}
A
root@ubuntu:~# echo ${ALPHA[3]}
D
------------------------------------
root@ubuntu:~# ls
functions.main tt
root@ubuntu:~# FILE=(*) #当前目录的内容
root@ubuntu:~# echo ${FILE}
functions.main


deckare -A DISK
WARNING =3
df |grep '^/dev/sd' > df.log
while read line ;do
INDEX=`echo $line |sed -rn 's@^([^[:space:]]) .*@\1@p'`
DISK[$INDEX]=`echo $line |sed -rn 's@^.* ([0-9]+)%.*@\1@p'`
if [ ${DISK[$INDEX]} -gt $SWANING ];then
wall $INDEX : ${DISK[$INDEX]}
fi
done < df.log


#生成10个随机数保存于数组中,并找出其最大值和最小值
#!/bin/bash
declare -i min max
declare -a nums
for ((i=0;i<10;i++));do
nums[$i]=$RANDOM
[ $i -eq 0 ] && min=${nums[$i]} && max=${nums[$i]}&& continue
[ ${nums[$i]} -gt $max ] && max=${nums[$i]}
[ ${nums[$i]} -lt $min ] && min=${nums[$i]}
done
echo "All numbers are ${nums[*]}"
echo Max is $max
echo Min is $min


#编写脚本,定义一个数组,数组中的元素对应的值是/var/log目录下所有以.log结尾的文件;统计出其下标为偶数的文件中的行数之和
#!/bin/bash
#
declare -a files
files=(/var/log/*.log)
declare -i lines=0
for i in $(seq 0 $[${#files[*]}-1]); do
if [ $[$i%2] -eq 0 ];then
let lines+=$(wc -l ${files[$i]} | cut -d' ' -f1)
fi
done
echo "Lines: $lines."

28.字符串切片

${#var}:返回字符串变量var的长度
${var:offset}:返回字符串变量var中从第offset个字符后(包括第offset个字符)的字符开始,到最后的部分,offset的取值在0 到 ${#var}-1 之间(bash4.2后,允许为负值)
${var:offset:number}:返回字符串变量var中从第offset个字符后(包括第offset个字符)的字符开始,长度为number的部分
${var: -length}:取字符串的最右侧几个字符
#注意:冒号后必须有一空白字符
${var:offset:-length}:从最左侧跳过offset字符,一直向右取到距离最右侧lengh个字符之前的内容
${var: -length:-offset}:先从最右侧向左取到length个字符开始,再向右取到距离最右侧offset个字符之间的内容
#注意:-length前空格

[root@openstick1 ~]#A="ABCDEFGHIJKLMN"
[root@openstick1 ~]#echo ${A: -6:-2}
IJKL
[root@openstick1 ~]#echo ${A: 4:-2}
EFGHIJKL
[root@openstick1 ~]#echo ${A: -4}
KLMN
[root@openstick1 ~]#echo ${A: 4:2}
EF
[root@openstick1 ~]#echo ${A: 4}
EFGHIJKLMN
[root@openstick1 ~]#echo ${#A}
14

28.1.字符串处理

root@ubuntu:~# basename /etc/fstab
fstab
root@ubuntu:~# dirname /etc/fstab
/etc
基于模式取子串
${var#*word}:其中word可以是指定的任意字符
功能:自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符串(含)之间的所有字符
${var##*word}:同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容
#示例:
file="var/log/messages"
${file#*/}: log/messages
${file##*/}: messages


${var%word*}:其中word可以是指定的任意字符
功能:自右而左,查找var变量所存储的字符串中,第一次出现的word, 删除字符串最后一个字符向左至第一次出现word字符串(含)之间的所有字符
file="/var/log/messages"
${file%/*}: /var/log
${var%%word*}:同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符
示例:
url=http://www.magedu.com:80
${url##*:} 80
${url%%:*} http


#查找替换
${var/pattern/substr}: 查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之
${var//pattern/substr}: 查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之
${var/#pattern/substr}: 查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之
${var/%pattern/substr}: 查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之
root@ubuntu:~# file="var/log/messages"
root@ubuntu:~# echo ${file/s/t}
var/log/metsages
root@ubuntu:~# echo ${file//s/t}
var/log/mettaget


#查找并删除
${var/pattern}: 删除var表示的字符串中第一次被pattern匹配到的字符串
${var//pattern}:删除var表示的字符串中所有被pattern匹配到的字符串
${var/#pattern}:删除var表示的字符串中所有以pattern为行首匹配到的字符串
${var/%pattern}:删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串
[root@openstick1 ~]#echo ${A}
ABCDEFGHIJKLMN
[root@openstick1 ~]#echo ${A/C}
ABDEFGHIJKLMN


#字符大小写转换
${var^^}:把var中的所有小写字母转换为大写
${var,,}:把var中的所有大写字母转换为小写
[root@openstick1 ~]#echo ${A}
ABCDEFGHIJKLMN
[root@openstick1 ~]#echo ${A,,}
abcdefghijklmn


----------------------------------------------------------------------------
取路径、文件名、后缀
[root@localhost mech.d]# file=/dir1/dir2/dir3/my.file.txt
[root @ lvs data]#echo ${file}
/dir1/dir2/dir3/my.file.txt
# 表示去掉左边
[root @ lvs data]#echo ${file#*/}
dir1/dir2/dir3/my.file.txt
[root @ lvs data]#echo ${file##*/}
my.file.txt
[root @ lvs data]#echo ${file#*.}
file.txt
[root @ lvs data]#echo ${file##*.}
txt
% 表示去掉右边
[root @ lvs data]#echo ${file%/*}
/dir1/dir2/dir3
[root @ lvs data]#echo ${file%%/*}
[root @ lvs data]#echo ${file%.*}
/dir1/dir2/dir3/my.file
[root @ lvs data]#echo ${file%%.*}
/dir1/dir2/dir3/my

#取子串及替换
[root @ lvs data]#echo ${file}
/dir1/dir2/dir3/my.file.txt
[root @ lvs data]#echo ${file:0:5}
/dir1
[root @ lvs data]#echo ${file:5:5}
/dir2
[root @ lvs data]#echo ${file/dir/path}
/path1/dir2/dir3/my.file.txt
[root @ lvs data]#echo ${file//dir/path}
/path1/path2/path3/my.file.txt
[root @ lvs data]#echo ${#file} #获取变量长度
27

29.高级变量用法-有类型变量

Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令,用于指定变量的类型,两个命令是等价的
#declare [选项] 变量名
-r 声明或显示只读变量
-i 将变量定义为整型数
-a 将变量定义为数组
-A 将变量定义为关联数组
-f 显示已定义的所有函数名及其内容
-F 仅显示已定义的所有函数名
-x 声明或显示环境变量和函数
-l 声明变量为小写字母 declare –l var=UPPER
-u 声明变量为大写字母 declare –u var=lower

30.eval命令

eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。
该命令适用于那些一次扫描无法实现其功能的变量.该命令对变量进行两次扫描
#示例:
[root@server ~]# CMD=whoami
[root@server ~]# echo $CMD
whoami
[root@server ~]# eval $CMD
root
[root@server ~]# n=10
[root@server ~]# echo {0..$n}
{0..10}
[root@server ~]# eval echo {0..$n}
0 1 2 3 4 5 6 7 8 9 10

31.间接变量引用

如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用
variable1的值是variable2,而variable2又是变量名,variable2的值为value,
间接变量引用是指通过variable1获得变量值value的行为
variable1=variable2
variable2=value

bash Shell提供了两种格式实现间接变量引用
eval tempvar=\$$variable1
tempvar=${!variable1}

示例:
[root@server ~]# N=NAME
[root@server ~]# NAME=TAOWENWU
[root@server ~]# N1=${!N}
[root@server ~]# echo $N1
TAOWENWU
[root@server ~]# eval N2=\$$N
[root@server ~]# echo $N2
TAOWENWU

32.创建临时文件

mktemp命令:创建并显示临时文件,可避免冲突
#mktemp [OPTION]... [TEMPLATE]
TEMPLATE: filenameXXX
X至少要出现三个
OPTION:
-d: 创建临时目录
-p DIR或--tmpdir=DIR:指明临时文件所存放目录位置

#示例:
mktemp /tmp/testXXX
tmpdir=`mktemp -d /tmp/testdirXXX`
mktemp --tmpdir=/testdir testXXXXXX

33.安装复制文件

install命令:
install [OPTION]... [-T] SOURCE DEST 单文件
install [OPTION]... SOURCE... DIRECTORY
install [OPTION]... -t DIRECTORY SOURCE...
install [OPTION]... -d DIRECTORY...创建空目录

#选项:
-m MODE,默认755
-o OWNER
-g GROUP

#示例:
install -m 700 -o wang -g admins srcfile desfile
install –m 770 –d /testdir/installdir

34.expect介绍

expect 是由Don Libes基于Tcl( Tool Command Language )语言开发的,主要应用于自动化交互式操作的场景,
借助expect处理交互的命令,可以将交互过程如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成。尤其适用
于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率
#expect 语法:
expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ] 选项
-c:从命令行执行expect脚本,默认expect是交互地执行的
#示例:expect -c 'expect "\n" {send "pressed enter\n"}'
-d:可以输出调试信息
#示例:expect -d ssh.exp

#expect中相关命令
spawn #启动新的进程
send #用于向进程发送字符串
expect #从进程接收字符串
interact #允许用户交互
exp_continue #匹配多个字符串在执行动作后加此命令


expect最常用的语法(tcl语言:模式-动作)
#单一分支模式语法:
expect "hi" {send "You said hi\n"}
匹配到hi后,会输出"you said hi",并换行
expect -c 'expect "hi" {send "You said hi\n"}'

#多分支模式语法:
expect "hi" { send "You said hi\n" } \
"hehe" { send "Hehe yourself\n" } \
"bye" { send "Good bye\n" }
匹配hi,hello,bye任意字符串时,执行相应输出。等同如下:
expect {
"hi" { send "You said hi\n"}
"hehe" { send "Hehe yourself\n"}
"bye" { send " Good bye\n"}
}

34.1范例

#!/usr/bin/expect
spawn scp /etc/fstab 192.168.8.100:/app
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "tww\n" }
}
expect eof

-----------------------------------------------------------
#!/usr/bin/expect
spawn ssh 192.168.8.100
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "tww\n" }
}
interact
#expect eof

-----------------------------------------------------------
#!/usr/bin/expect
set ip 192.168.8.100
set user root
set password tww
set timeout 10
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
interact

-----------------------------------------------------------
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
interact
#./ssh3.exp 192.168.8.100 root tww

-----------------------------------------------------------
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
set timeout 10
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
expect "]#" { send "useradd haha\n" }
expect "]#" { send "echo tww |passwd --stdin haha\n" }
send "exit\n"
expect eof
#./ssh4.exp 192.168.8.100 root tww

-----------------------------------------------------------
#!/bin/bash
ip=$1
user=$2
password=$3
expect <<EOF
set timeout 20
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
expect "]#" { send "useradd hehe\n" }
expect "]#" { send "echo tww |passwd --stdin hehe\n" }
expect "]#" { send "exit\n" }
expect eof
EOF
#./ssh5.sh 192.168.8.100 root tww
  • 收藏
  • 评论
  • 分享
  • 举报

上一篇:命令-tcpdump


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK