shell的编程结构体

本文目录:

1.1 shell函数

1.2 条件结构:if

1.3 条件结构:case

1.4 条件结构:select

1.5 循环结构:for

1.6 循环结构:while

1.7 循环结构:until

1.8 exit、break、continue和return

1.1 shell函数

在shell中,函数可以被当作命令一样执行,它是命令的组合结构体。可以将函数看成是一个普通命令或者一个小型脚本。

首先给出几个关于函数的结论:

(1).当在bash中直接调用函数时,如果函数名和命令名相同,则优先执行函数,除非使用command命令。例如:定义了一个名为rm的函数,在bash中输入rm执行时,执行的是rm函数,而非/bin/rm命令,除非使用"command rm ARGS"。

(2).当前shell定义的函数只能在当前shell使用,子shell无法继承父shell的函数定义。除非使用"export -f"将函数导出为全局函数。

(2).定义了函数后,可以使用unset -f移除当前shell中已定义的函数。

(3).除非出现语法错误,或者已经存在一个同名只读函数,否则函数的退出状态码是函数内部结构中最后执行的一个命令的退出状态码。

(4).可以使用typeset -f [func_name]或declare -f [func_name]查看当前shell已定义的函数名和对应的定义语句。使用typeset -F或declare -F则只显示当前shell中已定义的函数名。

(5).函数可以递归,递归层次可以无限。

函数的语法结构:

[ function ] name () compound-cmd [redirection]

上面的语法结构中定义了一个名为name的函数,关键字function是可选的,如果使用了function关键字,则name后的括号可以省略。compound-cmd是函数体,通常使用大括号{}包围,由于历史原因,大括号本身也是关键字,所以为了不产生歧义,函数体必须和大括号使用空格、制表符、换行符分隔开来。还可以指定可选的函数重定向功能,这样当函数被调用的时候,指定的重定向也会被执行。

例如:定义一个名为rm的函数,该函数会将传递的所有文件移动到"~/backup"目录下,目的是替代rm命令,避免误删除的危险操作。

[root@xuexi ~]# function rm () { [ -d ~/rmbackup ] || mkdir ~/rmbackup;/bin/mv -f $@ ~/rmbackup; } &>/dev/null

在调用rm函数时,只需是给rm函数传递参数即可。例如,要删除/tmp/a.log。

[root@xuexi ~]# rm /tmp/a.log

在执行函数时,会将执行可能输出的信息重定向到/dev/null中。

为了让函数在子shell(例如脚本)中也可以使用,使用export的"-f"选项将其导出为全局函数。取消函数的导出则使用export的"-n"选项。

export -f rm
export -n rm

关于shell函数,还有几个需要说明的知识点:

(6).shell函数也接受位置变量$0、$1、$2...,但函数的位置参数是调用函数时传递给函数的,而非传递给脚本的参数。所以脚本的位置变量和函数的位置变量是不同的,但是$0和脚本的位置变量$0是一致的。另外,函数也接受特殊变量"$#",和脚本的"$#"一样,它也表示位置变量的个数。

(7).函数体内部可以使用return命令,当函数结构体中执行到return命令时将退出整个函数。return后可以带一个状态码整数,即return n,表示函数的退出状态码,不给定状态码时默认状态码为0。

(8).函数结构体中可以使用local命令定义本地变量,例如:local i=3。本地变量只在函数内部(包括子函数)可见,函数外不可见。

1.2 条件结构:if

语法结构:

if test-commands1; then

commands1;

[elif test-commands2; then

commands2;]

...

[else

commands3;]

fi

if的判断很简单,一切都以返回状态码是否为0为判决条件。如果test-commands1执行后的退出状态码为0(不是其执行结果为0),则执行commands1部分的结构体,否则如果test-commands2返回0则执行commands2部分的结构体,如果都不满足,则执行commands3的结构体。

常见的test-commands有几种类型:

(1).一条普通的命令。只要该命令退出状态码为0,则执行then后的语句体。例如:

if echo haha &>/dev/null;then echo go;fi

(2).测试语句。例如test、[]、[[]]。

if [ $((1+2)) -eq 3 ];then echo go;fi
if [[ "$name" =~ "long" ]];then echo go;fi

(3).使用逻辑运算符,包括!、&&和||。该特性主要是为普通命令而提供,因为测试语句自身就支持逻辑运算。所以,对于测试语句就提供了两种写法,一种是将逻辑运算符作为测试语句的一部分,一种是将逻辑运算符作为if语句的一部分。例如:

if ! id "$name" &>/dev/null;then echo "$name" miss;fi
if ! [ 3 -eq 3 ];then echo go;fi
if [ ! 3 -eq 3 ];then echo go;fi
if [ 3 -eq 3 ] && [ 4 -eq 4 ] ;then echo go;fi
if [ 3 -eq 3 -a 4 -eq 4 ];then echo go;fi
if [[ 3 -eq 3 && 4 -eq 4 ]];then echo go;fi

注意,在if语句中使用()不能改变优先级,而是让括号内的语句成为命令列表并进入子shell运行。因此,要改变优先级时,需要在测试语句中完成。

1.3 条件结构:case

语法结构:

case word in

[ [(] pattern [| pattern]…)

command-list ;;]

esac

sysV风格的服务启动脚本是shell脚本中使用case语句最典型案例。例如:

case "$1" in
    start)
        start;;
    stop)
        stop;;
    restart)
        restart;;
    reload | force-reload)
        reload;;
    status)
        status;;
    *)
        echo $"Usage: $0 {start|stop|status|restart|reload|force-reload}"
        exit 2
esac

从上面的示例中,可以看出一些结论:

(1).case中的每个小分句都以双分号";;"结尾,但最后一个小分句的双分号可以省略。实际上,小分句除了使用";;"结尾,还可以使用";&"和";;&"结尾,只不过意义不同,它们用的不多,不过为了文章完整性,稍后还是给出说明。

(2).每个小分句中的pattern部分都使用括号"()"包围,只不过左括号"("不是必须的。

(3).每个小分句的pattern支持通配符模式匹配(不是正则匹配模式,因此只有3种通配元字符:"*"、"?"和[...]),其中使用"|"分隔多个通配符pattern表示满足其中一个pattern即可。例如"([yY] | [yY][eE][sS]])"表示即可以输入单个字母的y或Y,还可以输入yes三个字母的任意大小写格式。

set -- y;case "$1" in ([yY]|[yY][eE][sS]) echo right;;(*) echo wrong;;esac

其中"set -- string_list"的作用是将输入的string_list按照IFS分隔后分别赋值给位置变量$1、$2、$3...,因此此处是为$1赋值字符"y"。

(4).最后一个小分句使用的pattern是"*",表示无法匹配前面所有小分句时,将匹配该小分句。一般最后一个小分句都会使用"*"避免case语句无法匹配的情况,在shell脚本中,此小分句一般用于提示用户脚本的使用方法,即给出脚本的Usage。

(5).附加一个结论:如果任何模式都不匹配,该命令的返回状态是零;否则,返回最后一个被执行的命令的返回值。

如果小分句不是使用双分号";;"结尾,而是使用";&"或";;&"结尾,则case语句的行为将改变。

◇ ";;"结尾符号表示小分句执行完成后立即退出case语句。

◇ ";&"表示继续执行下一个小分句中的command部分,而无需进行匹配动作,并由此小分句的结尾符号来决定是否继续操作下一个小分句。

◇ ";;&"表示继续向后(不止是下一个,而是一直向后)匹配小分句,如果匹配成功,则执行对应小分句中的command部分,并由此小分句的结尾符号来决定是否继续向后匹配。

示例如下:

set -- y
case "$1" in
    ([yY]|[yY][eE][sS])
        echo yes;&
    ([nN]|[nN][oO])
        echo no;;
    (*)
        echo wrong;;
esac
yes
no

在此示例中,$1能匹配第一个小分句,但第一个小分句的结尾符号为";&",所以无需判断地直接执行第二个小分句的"echo no",但第二个小分句的结尾符号为";;",于是直接退出case语句。因此,即使$1无法匹配第二个小分句,case语句的结果中也输出了"yes"和"no"。

set -- y
case "$1" in
    ([yY]|[yY][eE][sS])
        echo yes;;&
    ([nN]|[nN][oO])
        echo no;;
    (*)
        echo wrong;;
esac
yes
wrong

在此示例中,$1能匹配第一个小分句,但第一个小分句的结尾符号为";;&",所以继续向下匹配,第二个小分句未匹配成功,直到第三个小分句才被匹配上,于是执行第三个小分句中的"echo wrong",但第三个小分句的结尾符号为";;",于是直接退出case语句。所以,结果中输出了"yes"和"wrong"。

1.4 条件结构:select

shell中提供菜单选择的条件判断结构。例如:

[root@xuexi ~]# select fname in cat dog sheep mouse;do echo your choice: \"$REPLY\) $fname\";break;done
1) cat
2) dog
3) sheep
4) mouse
#? 3                      # 在此选择序号3
your choice: "3) sheep"   # 将输出序号3对应的内容

语法结构:

select name [ in word ] ; do cmd_list ; done

它的结构几乎和for循环的结构相同。有以下几个要点:

(1).in关键词后的word将根据IFS变量进行分割,分割后的每一项都进行编号,作为菜单序号被输出,如果省略in word,则等价于"in $@",即将位置变量的内容作为菜单项。

(2).当选择菜单序号后,该序号的内容将保存到变量name中,并且所输入的内容(一般是序号值,例如上面的例子中输入的3,但不规定一定要输入序号值,例如随便输入几个字符)保存保存到特殊变量REPLY中。

(3).每次输入选择后,select语句都将重置,如果输入的菜单序号存在,则cmd_list会重新执行,变量name也将重置。如果没有break命令,则select语句会一直运行,如果遇到break命令,将退出select语句。

仍然是上面的示例:但不是用break

[root@xuexi ~]# select fname in cat dog sheep mouse;do echo your choice: \"$REPLY\) $fname\";done  
1) cat
2) dog
3) sheep
4) mouse
#? 2
your choice: "2) dog"
#? habagou                    # 随意输入几个字符
your choice: "habagou) "      # 变量fname被重置为空,变量REPLY被赋予了输入的值habagou
#? 2 3
your choice: "2 3) "   
#? ^C                         # 直到杀掉进程select才结束

1.5 循环结构:for

for循环再shell脚本中应用极其广泛,它有两种语法结构:

结构一:for name [ [ in [ word ... ] ] ; ] do cmd_list ; done

结构二:for (( expr1 ; expr2 ; expr3 )) ; do cmd_list ; done

结构一中:将扩展in word,然后按照IFS变量对word进行分割,并依次将分割的单词赋值给变量name,每赋值一次,执行一次循环体cmd_list,然后再继续将下一个单词赋值给变量name,直到所有变量赋值结束。如果省略in word,则等价于"in $@",即展开位置变量并依次赋值给变量name。注意,如果word中使用引号包围了某些单词,这引号包围的内容被分割为一个单词。

例如:

[root@xuexi ~]# for i in 1 2 3 4;do echo $i;done
1
2
3
4
[root@xuexi ~]# for i in 1 2 "3 4";do echo $i;done
1
2
3 4

结构二中:该结构的expr部分只支持数学计算和比较。首先计算expr1,再判断expr2的返回状态码,如果为0,则执行cmd_list,并将计算expr3的值,并再次判断expr2的状态码。直到expr2的返回状态码不为0,循环结束。

例如:

[root@xuexi ~]# for ((i=1;i<=3;++i));do echo $i;done
1
2
3
[root@xuexi ~]# for ((i=1,j=3;i<=3 && j>=2;++i,--j));do echo $i $j;done
1 3
2 2

1.6 循环结构:while

使用while循环尽量要让条件运行到可以退出循环,否则无限循环。一般都在命令体部分加上变量的改变行为。

语法结构:

while test_cmd_list; do cmd_list; done

首先执行test_cmd_list中的命令,当test_cmd_list的最后一个命令的状态码为0时,将执行一次cmd_list,然后回到循环的开头继续执行test_cmd_list。只有test_cmd_list中最后一个测试命令的状态码非0时,循环才会退出。

例如:计算1到10的算术和。

[root@xuexi ~]# let i=1,sum=0;while [ $i -le 10 ];do let sum=sum+i;let ++i;done;echo $sum         
55

在此例中,test_cmd_list中只有一个命令[ $i -le 10 ],所以它的状态直接决定整个循环何时退出。

test_cmd_list中可以是多个命令,但千万要考虑清楚,是否要让决定退出循环的测试命令处在列表的尾部,否则将进入无线循环。

[root@xuexi ~]# let i=1,sum=0;while echo $i;[ $i -le 10 ];do let sum=sum+i;let ++i;done;echo $sum 
1
2
3
4
5
6
7
8
9
10
11
55

对于while循环,有另外两种常见的写法:

(1).test_cmd_list部分使用一个冒号":"或者true命令,使得while进入无限循环。

while :;do # 或者"while true;do"

...

done

(2).使用read命令从标准输入中按行读取值,然后保存到变量line中(既然是read命令,所以可以保存到多个变量中),读取一行是一个循环。

由于标准输入既可以来源于重定向,也可以来源于管道(本质还是重定向),所以有两种常见的写法:

写法一:

while read line

do

...

done </path/filename

写法二:

echo "abc xyz" | while read field1 field2 # 按IFS分割,并赋给两个变量

do

...

done

既然是读取标准输入,于是还可以衍生出几种写法:

while read var;do ...;done < <(cmd_list) # 采用进程替换

exec <filename;while read var;do ...;done # 改变标准输入

1.7 循环结构:until

until和while循环基本一致,所不同的仅仅只是test_cmd_list的意义。

语法结构:

until test_cmd_list; do cmd_list; done

首先判断test_cmd_list中的最后一个命令,如果状态码为非0,则执行一次cmd_list,然后再返回循环的开头再次执行test_cmd_list,直到test_cmd_list的最后一个命令状态码为0时,才退出循环。

和while不同的是,当判断test_cmd_list最后一个命令的状态满足退出条件时直接退出循环,也就是说循环是在test_cmd_list最后一个命令处退出的。

例如:

[root@xuexi ~]# i=5;until echo haha;[ "$i" -eq 0 ];do let --i;echo $i;done
haha
4
haha
3
haha
2
haha
1
haha
0
haha

1.8 exit、break、continue和return

exit [n] :退出当前shell,在脚本中应用则表示退出整个脚本(子shell)。其中数值n表示退出状态码。

break [n] :退出整个循环,包括for、while、until和select语句。其中数值n表示退出的循环层次。

continue [n] :退出当前循环进入下一次循环。n表示继续执行第n次循环。

return [n] :退出整个函数。n表示函数的退出状态码。

回到系列文章大纲:http://www.cnblogs.com/f-ck-need-u/p/7048359.html

转载请注明出处:http://www.cnblogs.com/f-ck-need-u/p/7429462.html

注:若您觉得这篇文章还不错请点击下右下角的推荐,有了您的支持才能激发作者更大的写作热情,非常感谢!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


用的openwrt路由器,家里宽带申请了动态公网ip,为了方便把2280端口映射到公网,发现经常被暴力破解,自己写了个临时封禁ip功能的脚本,实现5分钟内同一个ip登录密码错误10次就封禁这个ip5分钟,并且进行邮件通知使用步骤openwrt为19.07.03版本,其他版本没有测试过安装bashmsmtpopkg
#!/bin/bashcommand1&command2&wait从Shell脚本并行运行多个程序–杨河老李(kviccn.github.io)
1.先查出MAMP下面集成的PHP版本cd/Applications/MAMP/bin/phpls-ls 2.编辑修改.bash_profile文件(没有.bash_profile文件的情况下回自动创建)sudovim~/.bash_profile在文件的最后输入以下信息,然后保存退出exportPATH="/Applications/MAMP/bin/php/php7.2.20/b
1、先输入locale-a,查看一下现在已安装的语言2、若不存在如zh_CN之类的语言包,进行中文语言包装:apt-getinstalllanguage-pack-zh-hans3、安装好后我们可以进行临时修改:然后添加中文支持: locale-genzh_CN.UTF-8临时修改> export LC_ALL='zh_CN.utf8'> locale永久
BashPerlTclsyntaxdiff1.进制数表示Languagebinaryoctalhexadecimalbash2#[0~1]0[0~7]0x[0~f]or0X[0~f]perl0b[0~1]0[0~7]0x[0~f]tcl0b[0~1]0o[0~7]0x[0~f]bashdifferentbaserepresntationreference2.StringlengthLanguageStr
正常安装了k8s后,使用kubect工具后接的命令不能直接tab补全命令补全方法:yum-yinstallbash-completionsource/usr/share/bash-completion/bash_completionsource<(kubectlcompletionbash)echo"source<(kubectlcompletionbash)">>~/.bashrc 
参考这里启动jar包shell脚本修改过来的#!/bin/bash#默认应用名称defaultAppName='./gadmin'appName=''if[[$1&&$1!=0]]thenappName=$1elseappName=$defaultAppNamefiecho">>>>>>本次重启的应用:$appName<
#一个数字的行#!/bin/bashwhilereadlinedon=`echo$line|sed's/[^0-9]//g'|wc-L`if[$n-eq1]thenecho$linefidone<1.txt#日志切割归档#!/bin/bashcd/data/logslog=1.logmv_log(){[-f$1]&&mv$1$2
#文件增加内容#!/bin/bashn=0cat1.txt|whilereadlinedon=[$n+1]if[$n-eq5]thenecho$lineecho-e"#Thisisatestfile.\n#Testinsertlineintothisfile."elseecho$linefidone#备份/etc目录#
# su - oraclesu: /usr/bin/ksh: No such file or directory根据报错信息:显示无法找到文件 /usr/bin/ksh果然没有该文件,但是发现存在文件/bin/ksh,于是创建了一个软连接,可以规避问题,可以成功切换到用户下,但无法执行系统自带命令。$. .bash_profile-ksh: .: .b
history显示历史指令记录内容,下达历史纪录中的指令主要的使用方法如果你想禁用history,可以将HISTSIZE设置为0:#exportHISTSIZE=0使用HISTIGNORE忽略历史中的特定命令下面的例子,将忽略pwd、ls、ls-ltr等命令:#exportHISTIGNORE=”pwd:ls:ls-ltr:”使用HIS
一.命令历史  1.history环境变量:    HISTSIZE:输出的命令历史条数,如history的记录数    HISTFILESIZE:~/.bash_history保存的命令历史记录数    HISTFILLE:历史记录的文件路径    HISTCONTROL:     ignorespace:忽略以空格开头的命令
之前在网上看到很多师傅们总结的linux反弹shell的一些方法,为了更熟练的去运用这些技术,于是自己花精力查了很多资料去理解这些命令的含义,将研究的成果记录在这里,所谓的反弹shell,指的是我们在自己的机器上开启监听,然后在被攻击者的机器上发送连接请求去连接我们的机器,将被攻击者的she
BashOne-LinersExplained,PartI:Workingwithfileshttps://catonmat.net/bash-one-liners-explained-part-oneBashOne-LinersExplained,PartII:Workingwithstringshttps://catonmat.net/bash-one-liners-explained-part-twoBashOne-LinersExplained,PartII
Shell中变量的作用域:在当前Shell会话中使用,全局变量。在函数内部使用,局部变量。可以在其他Shell会话中使用,环境变量。局部变量:默认情况下函数内的变量也是全局变量#!/bin/bashfunctionfunc(){a=99}funcecho$a输出>>99为了让全局变量变成局部变量
1、多命令顺序执行;  命令1;命令2  多个命令顺序执行,命令之间没有任何逻辑联系&&  命令1&&命令2  逻辑与,当命令1正确执行,才会执行命令2||  命令1||命令2  逻辑或,当命令1执行不正确,才会执行命令2例如:ls;date;cd/home/lsx;pwd;who ddif=输入文件of=输
原博文使用Linux或者unix系统的同学可能都对#!这个符号并不陌生,但是你真的了解它吗?首先,这个符号(#!)的名称,叫做"Shebang"或者"Sha-bang"。Linux执行文件时发现这个格式,会把!后的内容提取出来拼接在脚本文件或路径之前,当作实际执行的命令。 Shebang这个符号通常在Unix系统的脚本
1、历史命令history[选项][历史命令保存文件]选项:-c:  清空历史命令-w:  把缓存中的历史命令写入历史命令保存文件 ~/.bash_historyvim/etc/profile中的Histsize可改存储历史命令数量历史命令的调用使用上、下箭头调用以前的历史命令使用“!n”重复执行第n条历史
目录1.Shell脚本规范2.Shell脚本执行3.Shell脚本变量3.1环境变量3.1.1自定义环境变量3.1.2显示与取消环境变量3.1.3环境变量初始化与对应文件的生效顺序3.2普通变量3.2.1定义本地变量3.2.2shell调用变量3.2.3grep调用变量3.2.4awk调用变量3.3
   http://www.voidcn.com/blog/wszzdanm/article/p-6145895.html命令功能:显示登录用户的信息命令格式:常用选项:举例:w显示已经登录的用户及正在进行的操作[root@localhost~]#w 11:22:01up4days,21:22, 3users, loadaverage:0.00,0.00,0.00USER