shell实例示范手册
向世界说你好
简单的hello world:
赋值、语句赋值(除了反引号你可以使用$()来包裹语句)<br />echo -e 转义输出\n<br />;分割语句<br />str切片、连接<br />$[]运算符号:没有这个罩着你会发现在别的语言里轻松实现的计算在shell里面是多么的令人头疼!但是需要注意,方括号只适合整数计算
cat test1.sh
#!/bin/sh
var1=" hello world"
var2="nihao,$var1"
var3=`pwd;who`
var4=$(date +%y%m%d)
var5=$[1 + 5]
var6=$[$var5 * 2]
echo -e "${var2:0:5}\n"
echo $var3
echo $var4
echo $var6
# nihao
# /root/shtest root pts/0 2023-03-15 09:29 (47.96.60.109) root pts/1 2023-03-15 14:52 (47.96.60.109)
# 230315
# 12
浮点计算器bc
bc计算器可以解决浮点计算的问题,你可以在shell里面使用bc自由的使用他,当想退出的时候使用quit就行了
那么问题来了,要怎么在shell里面使用bc呢
var1=`echo "scale=5;17/4" | bc`
echo $var1
# 4.25000
scale是bc中定义保留小数位数的参数,使用;分隔开指令,通过管道符发给bc,就这么简单!
EOF
如果一条式子设计多步运算,那么一条条写管道符肯定不是什么美事,但是好在我们有内联重定向
var1=12.7
var2=3
var3=34.8
var5=`bc << EOF
scale=5
a1 = ($var1 * $var2)
b1 = ($var3 / $var2)
a1 + b1
EOF
`
echo "final result is $var5"
# final result is 49.70000
修改默认退出码
$?可以看到上个命令的退出状态,一般来讲0是正常退出,126是不可执行,127是没找到,130是中止,其他就是各种比较随意的错误了
exit命令可以让你修改默认的状态退出码,最大值255
.......
echo $?
exit 5
[root@master shtest]# ./bcc.sh
final result is 49.70000
0
[root@master shtest]# echo $?
5
结构化
if
如果if后面的语句退出状态码是0,那么位于then部分命令就会被执行
if pwd
then
echo "here"
fi
有if自然就会有else
if cat 123.txt
then
echo "here"
else
echo "404"
fi
当然,if也可以嵌套,或者也可使用elif,用法和if一样
elif当后面的语句退出为0时会去执行then
if grep alx /etc/passwd
then
echo "user alx exist"
elif ls -d /home/alx
then
echo "no user alx exist"
echo "but his home dir remains"
else
echo "no user alx exist"
fi
# [root@master shtest]# ./iffi.sh
# ls: cannot access /home/alx: No such file or directory
# no user alx exist
# [root@master shtest]# touch /home/alx
# [root@master shtest]# ./iffi.sh
# /home/alx
# no user alx exist
# but his home dir remains
值测试
数值比较,这其实是使用test指令,只不过需要注意的是,老毛病:shell只能处理整数
-eq 相等
-ge 大于等于
-gt 大于
-le 小于等于
-lt 小于
-ne 不等于
字符处理
str1 = str2 检查是否相等
str1 != str2 检查是否不等
str1 < str2 检查是否小于
str1 > str2 检查是否大于
-n "$str1" 检查长度是否为非0
-z "$str2" 检查长度是否为0
code=xxx
if test -n "$code" #这里也可以换成下面那种方括号形式
then
echo "code's value is $code "
else
echo "code is empty"
fi
user="bob"
admin="alic"
if [ $user = $admin ] #注意这里两边该死的括号和空格
then
echo "welcom $user"
else
echo "no way"
fi
文件比较
需要注意的是,linux允许文件或目录名包含空格,但是在脚本中空格会被理解成分隔,所以在处理对象时,最好使用""将其包起,如
[ -d "$test" ]
-d 检查文件是否存在并且是个目录
-e 检查是否存在
-f 检查是否存在并且是个文件
-r 检查是否存在并且可读
-s 检查是否存在并且非空
-w 检查是否存在并且可写
-x 检查是否存在并且可执行
-O 检查是否存在并且属于当前用户
-G 检查是否存在并且默认属组与当前用户相同
file1 -nt file2 检查是否比f2新,但如果文件不存在,会造成错误结果
file1 -ot file2 检查是否比f2旧
复合条件测试
[ xx ] && [ xxx ]
[ ss ] || [ sss ]
例子:判断文件是否存在且可写,如果满足条件则写入
#!/bin/sh
if [ -e ~/xx.txt ] && [ -w ~/xx.txt ]
then
echo "start to write"
cat<<EOF>~/xx.txt
line 1 in head
line 2 as body
line 3 is end
EOF
else
echo "The file not exist or not writeable"
fi
数组
扫描目录下是否有对应文件,并生成日志
#!/bin/bash
filearray=("test.sh" "ilt.sh" "init.sh")
for file in ${filearray[@]}
do
if [ -e "$file" ]
then
day=`date +%y%m%d`
echo "$file" >> "result$day.log"
fi
done > day.log
双括号高级运算
太高级了以至于我暂时不知道该在哪用
双方括号,用于定义正则表达式,部分shell不能支持
case
用于匹配值
#!/bin/bash
user=lisa
case $user in
lilei | alisi)
echo "access denied";;
lisa)
echo "welcom";;
*)
echo "wrong auth";;
esac
for
需要注意的是,for默认以空格作为每个元素的分隔,如果你想把一个带空格的复合词语当成一个而非两个对象进行处理,你需要使用""来包裹
或者,修改系统环境变量
IFS=$'\n'<br />如需多个,则在赋值时一行串起来就行了<br />IFS=$'\n':;"
list=`ls -l | grep -v total | awk '{print $NF}'`
echo $list
for file in $list
do
echo "Now scan $file"
done
另一种写法
for file in ~/shtest/*
do
echo "Now scan $file"
done
当然,for也可以写成c++风格
for (( i=1; i<5; i++))
do
echo $i
done
break\continue也可以使用
另外,输出也可以重定向
list=`ls -l | grep -v total | awk '{print $NF}'`
for file in $list
do
echo "Now scan $file"
done > result.log
循环嵌套
检查path中所有可执行脚本
IFSold=$IFS
IFS=:
for item in $PATH
do
for file in $item/*
do
if [ -x $file ]
then
echo $file
fi
done
done > result.log
IFS=$IFSold
while
count=`ls -l | head -n 1 | awk '{print $2}'`
echo $count
while [ $count -gt 20 ]
do
count=$[ $count - 5 ]
echo $count
done
while后面也可以接多个测试语句,但是只有最后一个语句会被当成结束循环的判断标准
until
until用法和while相同,只不过他是当满足测试条件的时候才会停止循环
交互
参数
$0表示脚本名,但如果你在用别的指令调用脚本时,如果使用的是绝对路径,那么这个值也会变成绝对路径,不过你可以使用basename来返回不包含路径的脚本名<br />$1-9表示第1-9个参数
当然同样,需要注意空格分隔符问题,最好用引号包裹
$#参数个数<br />$(!#)最后一个参数,如果你想直接用上面那个$#放进去,那么会直接报错<br />$*把所有的参数作为一个整体输出
$@类似数组,把所有参数作为一个可用for迭代的对象输出
shift指令会使得参数的值向左移一位,$3移到$2,而$1的值会被删除,值一旦被丢掉就再也无法恢复,请注意这点
很好,你现在已经学会如何处理参数了,来写个能够自由带参的程序吧(
开玩笑的,虽然能写但是还不够优雅,让我们再学两个小技巧
getopt 选项 参数
如果某个选项需指定具体的参数,那么在其后使用:
如果某个选项的参数可有可无,那么在其后使用::
如果你不想看到他报错,那么使用-q参数
[root@master ~]# getopt -q ab:cd -a -b test1 -c -d
-a -b 'test1' -c -d --
当然也可以使用长参数,使用逗号分隔,但是需要使用-o区分短参数,用-l区分长参数,使用--来分隔值和参数
[root@master ~]# getopt -q -o ab:cd -l str1:,str2,str3 -- -a -b test1 -c -d --str1 testing --str2 --str3
-a -b 'test1' -c -d --str1 'testing' --str2 --str3 --
另外一个指令,getopts,用法比getopt简单的多,他一次只会处理一个从命令行得到的值,将参数保存在opt里,将值保存在optarg里
getopts "a:bcd" opt
set指令将getopt的输出作为参数重新输入至这个脚本
set -- `getopt -q ab:c "$@"`
while [ -n $1 ]
do
case "$1" in
-a) echo "get -a opt";;
-b) param=$2
echo "get -b opt,value is $param"
shift;;
-c) echo "get -c opt";;
--) shift
break;;
*) echo "wrong option $1"
break;;
esac
shift
done
getopts智能很多,而且他允许值包含括号、把短选项放在一起使用
[root@master shtest]# cat opt.sh
while getopts "ab:c" opt
do
case $opt in
a) echo "get option -a";;
b) echo "get option -b ,value is $OPTARG";;
c) echo "get option -c";;
*) echo "no this option $opt";;
esac
done
[root@master shtest]# ./opt.sh -ac -b "test1 test2"
get option -a
get option -c
get option -b ,value is test1 test2
读取输入
read可以接收用户的输入,后面可以设置变量来接收,当用户输入值时,以分隔符为分界,依次把值填入变量中,如果值多于变量的个数,那么多余的值都会被填入最后一个变量里面。也可以不设变量,这样所有的值都会被填入默认环境变量REPLY当中
如果想设置输入超时,可以使用-t参数
如果想要用read在获取值的时候同时输出提示语句,可以使用-p
如果想获得定长的回答,可以使用-n,当输入的字符串长度到达要求时就会自动推进
[root@master shtest]# cat ./read.sh
read -n1 -t 10 -p "Do you want to continue?(Y/N)" answer
case $answer in
Y | y) echo "Ok,go on";;
N | n) echo "Well,as your wish";;
esac
[root@master shtest]# ./read.sh
Do you want to continue?(Y/N)yOk,go on
[root@master shtest]# ./read.sh
Do you want to continue?(Y/N)nWell,as your wish
[root@master shtest]# ./read.sh
Do you want to continue?(Y/N)t[root@master shtest]#
使用read配合cat可以实现按行读取文件
count=1
cat result.log | while read line
do
echo "This is line $count,$line"
count=$[ $count + 1 ]
done
重定向
将stdout和stderr合并后重定向到file
cmd > file 2>&1
永久重定向:使用exec,在脚本中设置一次就行
exec 2>log.file
#!/bin/sh
exec >>result.log 2>&1
cat $1 | wc -l
双向输出
如果你想同时向文件和stdout输出内容,可以使用tee,默认为覆盖,-a为追加
#!/bin/sh
cat $1 | wc -l | tee result.log
创建临时文件
mktemp file.XXXXXX
这个指令会去替换xxx的部分,随机生成一个本目录下没有的文件名,当使用-t参数时,会在tmp目录下生成,-d将生成目录
mktemp file.XXXXXX
file.DWqD13
信号
linux kill信号
信号 | 值 | 描述 |
---|---|---|
1 | SIGHUP | hang up 挂起进程 |
2 | SIGINT | interrupt 中断进程 |
3 | SIGQUT | quit 停止进程 |
9 | SIGKILL | 无条件终止进程 |
15 | SIGTERM | termination 尽可能终止进程 |
17 | SIGSTOP | 无条件停止 |
18 | SIGTSTP | 停止或暂停进程 |
19 | SIGCONT | 继续中止的进程 |
3和15会被默认忽视
ctrl+c = SIGINT
ctrl+z = SIGTSTP
trap可以拦截设定的信号,用别的命令来替代执行后面的处理步骤,信号可以用空格间隔列举多个,用数字也行
除了以上几个信号,还可以监控别的,比如脚本退出的EXIT
[root@master shtest]# cat trap.sh
#!/bin/sh
while [ -f $0 ]
do
trap "echo 'No ctrl c allowed!'" SIGINT
done
[root@master shtest]# sh trap.sh
^CNo ctrl c allowed!
^CNo ctrl c allowed!
^CNo ctrl c allowed!
^Z
[2]+ Stopped sh trap.sh
定时任务
at
-f 指定执行脚本文件
支持绝对和相对时间两种设置方式,具体格式参考
https://wangchujiang.com/linux-command/c/at.html
atrm可以删除任务
[root@master shtest]# at -f checkfile.sh 10:50
job 1 at Mon Mar 27 10:50:00 2023
[root@master shtest]# atq
1 Mon Mar 27 10:50:00 2023 a root
crontab
时间格式:分时日月星期,可以用作为占位符
etc下有几个预设的crontab文件夹,ls etc/cron就能看到,也可以直接编辑crontab文件,contab -l可以看到当前有哪些定时任务
https://wangchujiang.com/linux-command/c/crontab.html
函数
基础定义
shell有两种函数格式,当需要使用的时候直接调用函数就行了
函数调用默认的退出码跟普通指令一样,如果想要修改可以使用return
如果想要获取函数的输出,需要直接用变量来赋值
function name {
commands
}
name() {
commands
return $[ $value * 2 ] #修改退出码
}
result=$(name) #获取函数输出
尽管函数也使用$#,$0,$1这种作为参数,但是这和调用脚本本身的参数并不是一个东西,函数的参数是在脚本内部调用这个函数时后面加上的参数。当然,除了这个比较特殊,其他脚本内定义的全局变量你都可以使用,如果你想设置一个只在函数内部使用的临时,那可以用local来声明
另外,shell的函数不理解数组,如果你把数组作为他的参数,他只会去处理第一个值,所以你需要把数组的值拆开给他
#!/bin/bash
function func1 {
if [ $# -eq 2 ]
then
echo $[ $1 * $2 ]
fi
}
result=`func1 2 4`
echo $result
库
shell函数仅在当前shell有效,当你运行一个脚本,shell会创建一个子shell,如果你想在一个脚本里面使用另外一个脚本里的函数,可以使用source把那个文件加载进当前shell里面,source也可以直接简写成 . 操作符
#!/bin/bash
. ./cfunc.sh
result=`func1 2 4`
echo $result
如果你想在每次shell启动的时候自动把函数加载进来,可以把函数写进~/.bashrc里面
sed
常用指令
s/org/new/ #replace
n,md #delete
流处理器,在处理数据前先规定好一组规则,然后将指定的命令应用到输出流上,所以可以通过管道输入sed编辑器处理,如下面这个替换的小例子
root@master:~# echo "Have a good day"|sed 's/good/nice/'
Have a nice day
当然你也可以用它来编辑文件内容,但是在默认情况下,他修改完内容后会输出到标准输出,而不会修改原先的文件内容,如果想要使用多组修改规则,使用-e
root@master:~/shtest# cat re.log
This is line1
here is line2
Final
root@master:~/shtest# sed -e 's/This/Where/; s/here/There/' re.log
WThere is line1
There is line2
Final
root@master:~/shtest# cat re.log
This is line1
here is line2
Final
如果你需要处理的命令多,可以预先写一个.sed文件,然后使用sed -f xx.sed target来使用
sed可以配合替换标记使用,一共有四种替换标记
数字 表示替换文本中第几处匹配成功的字符
g 表示会替换所有匹配的文本
p 表示原先行的内容也会打印出来
w file 将替换结果写入文件,只会有发生替换的行
root@master:~/shtest# sed 's/is/is not/w chg.log' re.log
This not is line1
here is not line2
Final
root@master:~/shtest# cat chg.log
This not is line1
here is not line2
你也可以指定需要替换的行数,如果是想从某行到尾巴,那么可以用n,$ 在你不知道文本有多首航的时候这个技巧很有用
root@master:~/shtest# sed '1,3s/you/we/' chg.log
Here we can see line 5
Here we can see line 4
Here we can see line 3
Here you can see line 2
Here you can see line 1
当然,也可以用特征来匹配行,类似grep,用/string/来匹配,比如这里匹配5在的行
root@master:~/shtest# sed '/5/s/you/we/' chg.log
Here we can see line 5
Here you can see line 4
Here you can see line 3
Here you can see line 2
Here you can see line 1
sed可以使用d来删除指定行数
root@master:~/shtest# sed '2,4d' chg.log
Here you can see line 5
Here you can see line 1
d也可以用模式匹配,但是需要注意的是,他的开始功能可能会被多次触发,比如我想从含有xx的行删到kk的行,但是在kk之后又有一行出现了xx,那么删除功能会被再次打开,把xx后面所有的东西都删光(如果第二个xx一直到结尾都没有另外一个kk的话
评论已关闭