bash/shell编程学习(2)
先来复习上节重定向的用法:
1.快速清空文件
1 | cat demo.txt < /dev/null |
注:linux中有一个经典名言【一切皆文件】,/dev/null可以认为是一个特殊的空文件,更形象点,可以理解为科幻片中的黑洞,任何信息重向定输出到它后,便有去无回,当然黑洞里也没有信息能出来。
综合来讲,上面的意思就是利用<将黑洞做为demo.txt的标准输入,黑洞里没任何内容,任何文件里的内容被它吞噬了,自然也没就没东西能剩下了,所以最终就是demo.txt被黑洞洗空了。
/dev/null 还有其它用法,比如用它可以让nohup不生成nohup.out文件,见:http://www.cnblogs.com/yjmyzz/p/4831182.html
2.执行时输出源码
1 2 | #!/bin/bash -v printf '%0.2f\n' 12.12334 |
执行结果如下:
1 2 3 | #!/bin/bash -v printf '%0.2f\n' 12.12334 12.12 |
注意:第3行输出结果之前,把源码也打印出来了,秘密在于第1行最后的 -v 参数
3.调试模式
1 2 3 | #!/bin/bash -x printf '%0.2f\n' 12.12334 echo 'hello' |
执行结果如下:
1 2 3 4 | + printf '%0.2f\n' 12.12334 12.12 + echo hello hello |
注意:第一行后面的参数变成了-x,加上这个后,执行时,每一行代码在执行前,会先输出对应的源码,并且以+开头,十分方便调试。
4. if与test及[]
4.1 数字判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #!/bin/bash -x i=$1 #变量i的值取第1个参数的值 if test $i -gt 89; then #如果i>89 echo 'A' elif test $i -gt 79; then #如果i>79 echo 'B' elif test $i - eq 60 -o $i -gt 60; then #如果i=60或i>60(即:i>=60) echo 'C' elif test $i -gt 0; then #如果i>0 echo 'D' elif test $i -lt 0; then #如果i<0 echo 'invalid' else #i==0的情况 echo 'zero' fi |
注:if test 条件; then 语句 fi 这是基本格式,注意条件后的;不可省略,另外结束符号是fi(即:把if倒过来,有点回文的理念),另外要记住一堆缩写
-lt 即-Less Than的缩写,表示小于
-gt 即-Greater Than的缩写,表示大于
-eq 即-equal的缩写,表示等于,此外还有
-ne 即-Not Equal的缩写,表示不等于
-o 即-or,表示前后二个逻辑判断是『或』的关系,类似的
-a 即-and,表示前后二个逻辑判断是『与』的关系
elif 即else if的缩写
上面的示例运行结果:
1 2 3 4 5 | . /demo .sh 90 + i=90 + test 90 -gt 89 + echo A A |
test语句还有一个简化的写法,即把"test 条件"变成" [ 条件 ] ",注意二端的方括号左右都要加一个空格,所以上面的写法可以改成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | i=$1 if [ $i -gt 89 ]; then echo 'A' elif [ $i -gt 79 ]; then echo 'B' elif [ $i - eq 60 -o $i -gt 60 ]; then echo 'C' elif [ $i -gt 0 ]; then echo 'D' elif [ $i -lt 0 ]; then echo 'invalid' else echo 'zero' fi |
这样看起来就美观多了,如果不喜欢-o这种逻辑或的写法,第6行也可以换成这样
1 | elif [ $i - eq 60 ] || [ $i -gt 60 ]; then |
但是执行的细节略有区别,在调试模式下可以对比下,用||写法的输入(测试用例:61)
1 2 3 4 5 6 7 8 | . /demo2 .sh 61 + i=61 + '[' 61 -gt 89 ']' + '[' 61 -gt 79 ']' + '[' 61 - eq 60 ']' + '[' 61 -gt 60 ']' + echo C C |
而用-o写法的输出:
1 2 3 4 5 6 7 | . /demo2 .sh 61 + i=61 + '[' 61 -gt 89 ']' + '[' 61 -gt 79 ']' + '[' 61 - eq 60 -o 61 -gt 60 ']' + echo C C |
对比下5-6行可以发现,区别在于判断一次,还是判断二次
4.2 字符串判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #!/bin/bash -x str1= "abc" if [ -z "$str1" ]; then echo 'str1 is empty' else echo 'str1 is not empty' fi printf "\n" str2= "" if [ -n "$str2" ]; then echo 'str2 is not empty' else echo 'str2 is empty' fi printf "\n" if [ "$str1" = "$str2" ]; then echo 'str1 = str2' else echo 'str1 <> str2' fi |
注: -n即-not empty判断字符串非空,-z即-zero判断字符串为空,=判断字符串相同(判断字符串时,记得要加双引号)
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | + str1=abc + '[' -z abc ']' + echo 'str1 is not empty' str1 is not empty + printf '\n' + str2= + '[' -n '' ']' + echo 'str2 is empty' str2 is empty + printf '\n' + '[' abc = '' ']' + echo 'str1 <> str2' str1 <> str2 |
4.3 文件及目录判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/bin/bash -x if [ -f ~/.bash_profile ]; then echo '~/.bash_profile is a file' else echo '~/.bash_profile is not a file' fi printf '\n' if [ -d ~/ ]; then echo '~/ is a directory' else echo '~/ is not a directory' fi |
-f即判断是否为file, -d即判断是否为directory, 输出结果:
1 2 3 4 5 6 7 8 | + '[' -f /Users/yjmyzz/ .bash_profile ']' + echo '~/.bash_profile is a file' ~/.bash_profile is a file + printf '\n' + '[' -d /Users/yjmyzz/ ']' + echo '~/ is a directory' ~/ is a directory |
5.命令列表
命令1 && 命令2
解释:如果命令1返回成功,则命令2会执行,示例:
1 2 | #!/bin/bash ping -c 4 $1 && printf '\n==== %s connected ====\n' $1 |
将上面这段保存成testurl.sh,然后chmod +x testurl.sh,执行效果如下:
1 2 3 4 5 6 7 8 9 10 11 12 | . /testurl .sh www.baidu.com PING www.a.shifen.com (115.239.211.112): 56 data bytes 64 bytes from 115.239.211.112: icmp_seq=0 ttl=50 time =9.950 ms 64 bytes from 115.239.211.112: icmp_seq=1 ttl=50 time =23.994 ms 64 bytes from 115.239.211.112: icmp_seq=2 ttl=50 time =12.272 ms 64 bytes from 115.239.211.112: icmp_seq=3 ttl=50 time =19.717 ms --- www.a.shifen.com ping statistics --- 4 packets transmitted, 4 packets received, 0.0% packet loss round-trip min /avg/max/stddev = 9.950 /16 .483 /23 .994 /5 .641 ms ==== www.baidu.com connected ==== |
如果把后面的参数 ,换成某个不能访问的网址,比如在伟大的墙内,可以试下:
1 2 3 4 5 6 7 8 | . /testurl .sh www.google.com PING www.google.com (216.58.197.100): 56 data bytes Request timeout for icmp_seq 0 Request timeout for icmp_seq 1 Request timeout for icmp_seq 2 --- www.google.com ping statistics --- 4 packets transmitted, 0 packets received, 100.0% packet loss |
命令1 || 命令2
解释:这个正好跟&&相反,如果命令1返回失败,则执行命令2
1 2 | #!/bin/bash ping -c 4 $1 || printf '\n==== %s connect fail ====\n' $1 |
把这个保存成testurl2.sh ,然后重复刚才的测试
1 2 3 4 5 6 7 8 9 10 | . /testurl2 .sh www.google.com PING www.google.com (216.58.199.4): 56 data bytes Request timeout for icmp_seq 0 Request timeout for icmp_seq 1 Request timeout for icmp_seq 2 --- www.google.com ping statistics --- 4 packets transmitted, 0 packets received, 100.0% packet loss ==== www.google.com connect fail ==== |
通过刚才的测试,相信大家已经掌握&&与||的用法了,那么问题来了,如何判断前一个命令的执行结果是【成功】还是【失败】呢?
先回忆一下,大学里《C程序设计》里老师讲的内容,C程序里main函数,如果运行成功,最后一般会约定返回return 0,没错bash里就是这么判断的
(再提一个问题:为什么要跟C扯上关系?因为linux里的很多bash命令,就是拿C/C++来开发的),我们可以来验证下:
1 2 3 4 5 6 | #include <stdio.h> #include <stdlib.h> int main( int argc, char **argv){ printf ( "hello world and this function will return 0\n" ); return 0; } |
这是一段c语言的代码,保存成hello1.c,然后输入gcc -o hello1 hello1.c (mac本上只要安装了xcode,就已经自带了gcc编译器),然后会在当前目录下生成hello1的可执行文件,做为对比,再来一个hello2.c
1 2 3 4 5 6 | #include <stdio.h> #include <stdlib.h> int main( int argc, char **argv){ printf ( "hello world and this function will return 1\n" ); return 1; } |
同样编译成hello2,然后测试:
1 2 3 | . /hello1 && echo 'hello1 is ok' hello world and this function will return 0 hello1 is ok |
再来一个
1 2 | . /hello2 && echo 'you can not see this' hello world and this function will return 1 |
小结:这跟很多语言里约定1代表true, 0代表false正好是反的,在bash里,如果一个命令执行后返回0,表示成功,返回1表示失败。
6. 检测参数个数及类型
最后结合前面学到的知识,做一个小小的综合练习:
1 2 3 4 5 6 7 8 9 | #!/bin/bash echo 'param count: ' $ # echo 'first param: ' $1 if [ $ # -eq 1 ] && (echo $1 | grep ^[0-9]*$ >/dev/null); then echo 'param check pass!' else echo 'only one integer parameter is accepted!' fi |
上面这段代码的意思是仅接收1个整型的参数,将这段代码保存成check1.sh,然后试着运行下:
1 2 3 4 | . /check1 .sh a b 2 param count: 3 first param: a only one integer parameter is accepted! |
再试下:
1 2 3 4 | . /check1 .sh 123 param count: 1 first param: 123 param check pass! |
第5行的那个长长的if判断,初次看估计比较晕,我们来分解一下:
第一部分
1 | [ $ # -eq 1] |
其中$#表示参数的个数,-eq 1 要求参数个数必须等于1
第二部分
1 | ( echo $1 | grep ^[0-9]*$ > /dev/null ) |
仍然有点复杂,再细分一下,先不管最后的>/dev/null,将其去掉,然后简化一下:
grep 用于字符查找及过滤,见下面的图:
who用于显示本机有哪些用户登录了,以及登录的终端信息,加上管道符|,将输出结果传递给grep 001 ,最后就从who的一堆结果中,过滤出包含001的信息了。
再回过头,看下这个:
1 | echo 123 | grep ^[0-9]*$ |
会输出123(注:如果mac上将终端改成了zsh,直接运行会报错zsh: no matches found: ^[0-9]*$,解决办法:新建一个.sh脚本文件,写在脚本文件里就能运行了),grep后的部分是一个正则表达式,匹配0-9中的1个或多个,最后再来看:
1 | ( echo $1 | grep ^[0-9]*$ > /dev/null ) |
现在应该能看懂了吧,将1个参数输出,然后做为grep的输入,正常情况下,如果第1个参数为数字,则会输出,但是我们的本意是放在if条件判断中,并不希望将其输出,所以最后重定向到黑洞。
结合前面的命令列表&&,可以将这段if简化成终极版本:
1 2 3 4 | #!/bin/bash -x ! ([ $ # -eq 1 ] && (echo $1 | grep ^[0-9]*$ >/dev/null)) && echo 'only one integer parameter is accecpted ' && exit 1 echo 'param check pass!' |
就不解释了,大家自己体会吧。
出处:http://yjmyzz.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· Open-Sora 2.0 重磅开源!
2013-01-13 MAC本遭遇ARP攻击的处理办法
2010-01-13 windows 2008 使用注意事项