笔记·Shell基础知识
脚本调试
#bash -n /path/to/some_script #只检测,不真正执行;
#bash -x /path/to/some_script #调试并执行
变量声明和赋值
name=value
declare -x name=value
export name=value
TITLE+=:wang #变量追加
只读变量
declare -r name
readonly name
位置变量
$1, $2, ... 对应第1个、第2个等参数,shift [n]换位置
$0 命令本身,包括路径
$* 传递给脚本的所有参数,全部参数合为一个字符串
$@ 传递给脚本的所有参数,每个参数为独立字符串
$# 传递给脚本的参数的个数
注意:$@ $* 只在被双引号包起来的时候才会有差异
#set -- #清空所有位置变量
变量引用:${var_name},$var_name
#cat /proc/1235/environ 查看进程的环境变量:
脚本安全和set:
-u 在扩展一个没有设置的变量时,显示错误信息, 等同set -o nounset
-e 如果一个命令返回一个非0退出状态值(失败)就退出, 等同set -o errexit
格式输出:
printf "指定的格式" "文本1" ”文本2“……
%s 字符串
%d,%i 十进制整数
%f 浮点格式
%#s 中的数字代表此替换符中的输出字符宽度,不足补空格,默认是右对齐,%-10s表示10个字符宽,- 表示左对齐
%03d 表示3位宽度,不足前面用0补全,超出位数原样输出
%.2f 中的2表示小数点后显示的小数位数
范例:
#printf "%s\n" 1 2 3 4
#printf "%s %s\n" 1 2 3 4
#printf " (%s) " 1 2 3 4;echo ""
#printf "%-10s %-10s %-4s %s \n" 姓名 性别 年龄 体重 小明 男 20 70 小红 女 18 50
算数运算:
(1) let var=算术表达式
(2) ((var=算术表达式)) 和上面等价
(3) var=$[算术表达式]
(4) var=$((算术表达式))
(5) var=$(expr arg1 arg2 arg3 ...)
(6) declare -i var = 数值
(7) echo '算术表达式' | bc
#echo $[$RANDOM%50] #生成随机数
#let i=10*2
#((j=i+10))
#let i+=20 #相当于let i=i+20
#echo "scale=3;20/3"|bc
#expr 2 \* 3
逻辑运算:
与运算
或运算
非运算
异或运算
条件测试:
test EXPRESSION
[ EXPRESSION ] #和test 等价,建议使用 [ ]
[[ EXPRESSION ]] 相关于增强版的 [ ], 支持[]的用法,且支持扩展正则表达式和通配符
变量测试:
#注意 [ ] 需要空格,否则会报下面错误:-bash: [-v: command not found
[ -v NAME ] #判断 NAME 变量是否定义
[ -R NAME ] #判断 NAME 变量是否定义并且是名称引用,bash 4.4新特性
数值测试:
-eq 是否等于
-ne 是否不等于
-gt 是否大于
-ge 是否大于等于
-lt 是否小于
-le 是否小于等于
算术表达式比较:== != <= >= < >
字符串测试:
test和[ ]用法:
-z STRING 字符串是否为空,没定义或空为真,不空为假,
-n STRING 字符串是否不空,不空为真,空为假
STRING 同上
STRING1 = STRING2 是否等于,注意 = 前后有空格
STRING1 != STRING2 是否不等于
> ascii码是否大于ascii码
< 是否小于
[[ expression ]]用法:
== 左侧字符串是否和右侧的PATTERN相同,注意:此表达式用于[[ ]]中,PATTERN为通配符
=~ 左侧字符串是否能够被右侧的正则表达式的PATTERN所匹配,注意: 此表达式用于[[ ]]中;扩展的正则表达式
#结论:[[ == ]] == 右侧的 * 做为通配符,不要加“”,只想做为*, 需要加“” 或转义
#[[ $SCORE =~ ^(100|[0-9]{1,2})$ ]] #判断成绩是否理想
#[[ "$FILE" == *.log ]] #判断文件后缀
#[[ "$FILE" == *.log ]]
#[[ "$N" =~ ^[0-9]+$ ]] #判断是否为非负整数
#[[ $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是否合法
文件测试:
存在性测试:
-a FILE:同 -e
-e FILE: 文件存在性测试,存在为真,否则为假 #[ ! -e /etc/issue ]
-b FILE:是否存在且为块设备文件
-c FILE:是否存在且为字符设备文件
-d FILE:是否存在且为目录文件
-f FILE:是否存在且为普通文件
-h FILE 或 -L FILE:存在且为符号链接文件
-p FILE:是否存在且为命名管道文件
-S FILE:是否存在且为套接字文件
文件权限测试:
-r FILE:是否存在且可读
-w FILE: 是否存在且可写
-x FILE: 是否存在且可执行
-u FILE:是否存在且拥有suid权限
-g FILE:是否存在且拥有sgid权限
-k FILE:是否存在且拥有sticky权限
文件属性测试
-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
关于()和{}:
( list ) 会开启子shell,并且list中变量赋值及内部命令执行后,将不再影响后续的环境帮助参看:man bash 搜索(list)
{ list; } 不会启子shell, 在当前shell中运行,会影响当前shell环境帮助参看:man bash 搜索{ list; }
组合测试条件:
[ EXPRESSION1 -a EXPRESSION2 ] 并且,EXPRESSION1和EXPRESSION2都是真,结果才为真
[ EXPRESSION1 -o EXPRESSION2 ] 或者,EXPRESSION1和EXPRESSION2只要有一个真,结果就为真
[ ! EXPRESSION ] 取反
COMMAND1 && COMMAND2 #并且,短路与,代表条件性的AND THEN;如果COMMAND1 成功,将执行COMMAND2,否则,将不执行COMMAND2
COMMAND1 || COMMAND2 #或者,短路或,代表条件性的OR ELSE;如果COMMAND1 成功,将不执行COMMAND2,否则,将执行COMMAND2
! COMMAND #非,取反
read命令:
-p 指定要显示的提示
-s 静默输入,一般用于密码
-n N 指定输入的字符长度N
-d '字符' 输入结束符
-t N TIMEOUT为N秒
#read -p "Please input your name: " NAME
#read x y z <<< "I love you"
bash shell:
profile类:
全局:对所有用户有效
/etc/profile
/etc/profile.d/*.sh
个人:仅对当前用户有效
~/.bash_profile
功能:定义环境变量,运行命令或脚本
bashrc类:
全局:对所有用户有效
/etc/bashrc
个人:仅对当前用户有效
~/.bashrc
功能:定义本地变量,定义命令别名
交互式登录shell进程:
/etc/profile-->/etc/profile.d/*-->~/.bash_profile-->~/.bashrc-->/etc/bashrc
非交互式登录shell进程:
~/.bashrc-->/etc/bashrc-->/etc/profile.d/*
流程控制:
(1)选择执行 if 语句:
if 判断条件1; then
条件1为真的分支代码
elif 判断条件2; then
条件2为真的分支代码
elif 判断条件3; then
条件3为真的分支代码
...
else
以上条件都为假的分支代码
fi
(2)条件判断 case 语句:
case $INPUT in
y|yes)
echo "You input is YES"
;;
n|no)
echo "You input is NO"
;;
*)
echo "Input fales,please input yes or no!"
esac
(3)循环for
for 变量名 in 列表
do
循环体
done
范例1:
#sum=0;for i in {1..100};do let sum+=i;done ;echo sum=$sum
#seq -s+ 1 2 100| bc
#echo {1..100..2}|tr ' ' + | bc
范例2:
for i in {1..10};do
useradd user$i
PASS=`cat /dev/urandom | tr -dc '[:alnum:]' |head -c12`
echo $PASS | passwd --stdin user$i &> /dev/null
echo user$i:$PASS >> /data/user.log
echo "user$i is created"
done
(4)循环while
无线循环范例:
while true; do
循环体
done
while : ; do
循环体
done
范例2:
WARNING=10
touch deny_hosts.txt
while true;do
ss -nt | sed -nr '1!s#.* ([0-9.]+):[0-9]+ *#\1#p'|sort |uniq -c|sort |
while read count ip;do
if [ $count -gt $WARNING ];then
echo $ip is deny
grep -q "$ip" deny_hosts.txt || { echo $ip >> deny_hosts.txt;iptables -A INPUT -s $ip -j REJECT; }
fi
done
sleep 10
done
(5)循环until
until CONDITION; do
循环体
done
范例1:
#sum=0;i=1;until ((i>100));do let sum+=i;let i++;done;echo $sum
(6)循环控制continue:continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层
while CONDITION1; do
CMD1
...
if CONDITION2; then
continue
fi
CMDn
...
done
(7)循环控制语句 break:break [N]:提前结束第N层整个循环,最内层为第1层
while CONDITION1; do
CMD1
...
if CONDITION2; then
break
fi
CMDn
...
done
(8)循环控制 shift:shift [n] 用于将参量列表 list 左移指定次数,缺省为左移一次。
参量列表list一旦被移动,最左端的那个参数就从列表中删除。while循环遍历位置参量列表时,常用到shift
范例1:
#!/bin/bash
#step through all the positional parameters
until [ -z "$1" ]
do
echo "$1"
shift
done
echo
./shfit.sh a b c d e f g h
(9)while 特殊用法 while read:遍历文件或文本的每一行
while read line; do
循环体
done < /PATH/FROM/SOMEFILE
(10)循环与菜单 select
select variable in list ;do
循环体命令
done
函数 function:
定义函数:
#语法一:
func_name (){
...函数体...
}
语法二:
function func_name {
...函数体...
}
#语法三:
function func_name () {
...函数体...
}
查看函数:
#查看当前已定义的函数名
declare -F
#查看当前已定义的函数定义
declare -f
#查看指定当前已定义的函数名
declare -f func_name
#查看当前已定义的函数名定义
declare -F func_name
删除函数:
unset func_name
使用函数文件:
. filename
source filename
函数返回值:
默认取决于函数中执行的最后一条命令的退出状态码
自定义退出状态码,其格式为:
return 从函数中返回,用最后状态命令决定返回值
return 0 无错误返回
return 1-255 有错误返回
函数参数:
传递参数给函数:在函数名后面以空白分隔给定参数列表即可,如:testfunc arg1 arg2 ...
在函数体中当中,可使用$1, $2, ...调用这些参数;还可以使用$@, $*, $#等特殊变量
函数变量:
普通变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
环境变量:当前shell和子shell有效
本地变量:函数的生命周期;函数结束时变量被自动销毁
local NAME=VALUE
注意:
如果函数中定义了普通变量,且名称和局部变量相同,则使用本地变量
由于普通变量和局部变量会冲突,建议在函数中只使用本地变量
函数递归:
函数内部自已调用自已
必须有结束函数的出口语句,防止死循环
#!/bin/bash
fact() {
if [ $1 -eq 0 -o $1 -eq 1 ]; then
echo 1
else
echo $[$1*$(fact $[$1-1])]
fi
}
fact $1
fork 炸弹:
:(){ :|:& };:
bomb() { bomb | bomb & }; bomb
脚本相关工具:
信号捕捉trap:
#进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作
trap '触发指令' 信号
#忽略信号的操作
trap '' 信号
#恢复原信号的操作
trap '-' 信号
#列出自定义信号操作
trap -p
#当脚本退出时,执行finish函数
trap finish EXIT
创建临时文件 mktemp:
mktemp 命令用于创建并显示临时文件,可避免冲突
mktemp [OPTION]... [TEMPLATE] #TEMPLATE: filenameXXX,X至少要出现三个
-d #创建临时目录
-p DIR或--tmpdir=DIR #指明临时文件所存放目录位置
安装复制文件 install:
install 功能相当于cp,chmod,chown,chgrp ,mkdir 等相关工具的集合
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
-d DIRNAME 目录
交互式转化批处理工具 expect
expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ]
-c:从命令行执行expect脚本,默认expect是交互地执行的
-d:可以调试信息
expect中相关命令:
spawn 启动新的进程
expect 从进程接收字符串
send 用于向进程发送字符串
interact 允许用户交互
exp_continue 匹配多个字符串在执行动作后加此命令
范例1:非交互式复制文件
#!/usr/bin/expect
spawn scp /etc/redhat-release 10.0.0.7:/data
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "magedu\n" }
}
expect eof
范例2:自动登录
#!/usr/bin/expect
spawn ssh 10.0.0.7
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "magedu\n" }
}
interact
数组 array:
声明数组:
#普通数组可以不事先声明,直接使用
declare -a ARRAY_NAME
#关联数组必须先声明,再使用
declare -A ARRAY_NAME
数组赋值:
ARRAY_NAME[INDEX]=VALUE
weekdays[0]="Sunday"
weekdays[4]="Thursday"
ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)
title=("ceo" "coo" "cto")
num=({0..10})
alpha=({a..g})
file=( *.sh )
ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)
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
ARRAY[${#ARRAY[@]}]=value
关联数组:
declare -A ARRAY_NAME
ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)
字符串处理:
字符串切片:
1 基于偏移量取字符串:
#返回字符串变量var的字符的长度,一个汉字算一个字符
${#var}
#返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,到最后的部分,offset的取值在0 到 ${#var}-1 之间(bash4.2后,允许为负值)
${var:offset}
#返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,长度为number的部分
${var:offset:number}
#取字符串的最右侧几个字符,取字符串的最右侧几个字符, 注意:冒号后必须有一空白字符
${var: -length}
#从最左侧跳过offset字符,一直向右取到距离最右侧lengh个字符之前的内容,即:掐头去尾
${var:offset:-length}
#先从最右侧向左取到length个字符开始,再向右取到距离最右侧offset个字符之间的内容,注意:-length前空格,并且length必须大于offset
${var: -length:-offset}
2 基于模式取子串
#其中word可以是指定的任意字符,自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符串(含)之间的所有字符,即懒惰模式,以第一个word为界删左留右
${var#*word}
#同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容,即贪婪模式,以最后一个word为界删左留右
${var##*word}
#其中word可以是指定的任意字符,功能:自右而左,查找var变量所存储的字符串中,第一次出现的word,删除字符串最后一个字符向左至第一次出现word字符串(含)之间的所有字符,即懒惰模式,以从右向左的第一个word为界删右留左
${var%word*}
#同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符,即贪婪模式,以从右向左的最后一个word为界删右留左
${var%%word*}
查找替换:
#查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之
${var/pattern/substr}
#查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之
${var//pattern/substr}
#查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之
${var/#pattern/substr}
#查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之
${var/%pattern/substr}
查找并删除:
#删除var表示的字符串中第一次被pattern匹配到的字符串
${var/pattern}
删除var表示的字符串中所有被pattern匹配到的字符串
${var//pattern}
删除var表示的字符串中所有以pattern为行首匹配到的字符串
${var/#pattern}
删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串
${var/%pattern}
字符大小写转换:
#把var中的所有小写字母转换为大写
${var^^}
#把var中的所有大写字母转换为小写
${var,,}
高级变量:
有类型变量:
declare [选项] 变量名
-r 声明或显示只读变量
-i 将变量定义为整型数
-a 将变量定义为数组
-A 将变量定义为关联数组
-f 显示已定义的所有函数名及其内容
-F 仅显示已定义的所有函数名
-x 声明或显示环境变量和函数,相当于export
-l 声明变量为小写字母 declare -l var=UPPER
-u 声明变量为大写字母 declare -u var=lower
-n make NAME a reference to the variable named by its value