内容
编译型语言
解释器
变量:本地变量、环境变量、参数变量
条件:字符串判断、算术、文件测试
控制结构:循环、case
函数
脚本调用脚本,c语言调用脚本
awk,sed
环境变量
类型
语法
注意点
赋值
var=value
不能有空格
引用
$var
用双引号包裹是读var的值,用单引号包裹是$var
这4个字符。
删除
unset var
不要加$
符
输入
read var1
var2
...
按顺序写入变量,类似于scanf,以回车或者空格分隔
列出所有变量
set
全局化变量
export var
特殊变量
都需要搭配$
使用
特殊变量
定义
注意点
?
前一命令的退出状态
0代表成功、真;其他非0数代表失败、假的某一状态
$
当前Shell的进程ID
echo $$
才能输出
!
后台运行命令的进程ID
1 ~ 9
当前脚本的第1到9个参数
_
上一个命令的最后一个参数
比如在命令行敲入ls -a
,则echo $_
输出-a
PS1
Shell主提示符
PS2
Shell次提示符
字符串处理
注意双引号、单引号的功能,双引号引起主要是为了消除空格的影响,保留转义效果;而单引号是把所引的内容原封不动地 保留。
1 2 3 4 5 6 a="xcg" str="$a " echo "$str " str='$a' echo "$str "
如果想把某一个命令的运行结果作为字符串返回给str,则有两种方式。
$( )
2. 反引号
export
在当前shell可以再启动一个shell。如果我们在之前的shell中定义了局部变量,比如var=hello
,在新启动的shell是看不到的。如果要新shell看到,需要export var
。
其实新开的这个shell是在之前的shell新运行的一个程序。他俩是不同的进程。同理,如果想让其他进程也能看到之前shell的环境变量,此时就需要export。
第一行注释
第一行需要添加注释,指示使用哪个shell程序运行该脚本。
C语言编程main函数的参数 - 结合环境变量
1 2 3 4 5 6 7 8 9 10 #include <stdio.h> int main (int ac, char * av[]) { if (ac > 1 ) { printf ("%s\n" , av[1 ]); } return 0 ; }
运行结果:
1 2 mrcan@ubuntu:~$ ./c.out var var
如上,var是命令中的参数,程序输出了var这个参数名。
1 2 3 mrcan@ubuntu:~$ VAR=ThisIsMyVar mrcan@ubuntu:~$ ./c.out $VAR ThisIsMyVar
如上,也可以先定义环境变量VAR。然后再通过$VAR
传到main函数。
结合重定向 - 把另一个程序的结果作为参数传到main
结合《Linux_重定向》一文中的知识。
可以利用“命令代换”(形如$(./a.out)
)。
1 2 3 4 mrcan@ubuntu:~$ ./a.out C Program! mrcan@ubuntu:~$ ./c.out $(./a.out) C
如上,把a.out
的输出结果通过$(a.out)
传到了c.c
的main函数。
系统中的小程序,也可以作为命令代换的参数。
输出结果:
1 2 3 4 5 6 mrcan@ubuntu:~$ pwd /home/mrcan mrcan@ubuntu:~$ ls $(pwd ) a.c b.out c.out Documents Music Public test.cpp a.out build c.sh Downloads packages Templates test_muduo.cpp b.c c.c Desktop mprpc Pictures test Videos
Operator - 操作符
Equal Operator
=
string。注意,=
前后必须有空格分隔。不然就成了环境变量赋值了。
!=
string
-eq
numeral
-ne
numeral
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ``` 1. `-a`:and 2. `-o`:or 3. `!`:not 1. `-gt`:greater than 2. `-ge`:greater equal 3. `-lt`:less than 4. `-le`:less equal ```sh VAR1="COMPUTER" if [ $VAR ! = "COM" ]then echo "Y" else echo "N" fi
注意点:
=
前后有无空格的行为是不一样的。
中括号的前后最好也留上空格,避免无法分辨。
1 2 3 4 5 6 7 8 9 #! /bin/sh VAR1="COMPUTER" if [ $VAR1 ="COM" ] then echo "Y" else echo "N" fi
以上程序输出Y,即使VAR1不是"COM"。就是因为=
前后无空格。
if后跟的[ ... ]
相当于:(test ...)
循环
要注意的是对计数变量的处理
有2种方式:
let
(( ))
,双括号中是想要执行的命令/表达式,可以用$取表达式的值。
反引号:`expr …`
1 2 3 i=1 a=`expr $i \* 2` i=`expr $i + 1`
for
for循环的次数由in后面值的数目决定
1 2 3 4 5 6 7 8 9 10 for i in 1 2 3do echo i=$i sleep 1 done for name in $(ls )do echo "filename: $name " done
$@
1 2 3 4 5 #! /bin/sh for x in $@ do echo $x done
输出结果
while
判断条件,满足则循环执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 while [ 1 ]do echo "run" sleep 1 done while true do echo "input" read line if [ "$line " = end ] then break fi echo "line=$line " done while [ "$i " -lt 10 ]do echo "i=$i " i=`expr $i + 1` done
until
条件没满足时,循环执行;一旦条件满足则退出。
1 2 3 4 5 6 7 until [ -f file.txt ]do echo "not find file.txt" sleep 1 done echo "find file.txt"
read
read命令 -n(不换行) -p(提示语句) -n(字符个数)-t(等待时间) -s(不回显)
case
结合正则表达式的示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 while true do echo "input:" read line case "$line " in yes | Y) echo "this is yes" ;; no | N) echo "this is no" ;; end) break ;; *) echo "$line " ;; esac done read linecase "$line " in [Yy][Ee][Ss] | [Yy])echo "this is yes" ;; [Nn][Oo] | [Nn]) echo "this is no" ;; end) break ;; *) echo "$line " ;; esac
练习:把多行IP地址转换为10进制
先生成n行(192.168.0.1开始)
对n个ip地址进行转换,转换后的数据放在IPint.txt
文件内。
生成n行IP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #! /bin/sh FILE="IPs.txt" I=1 if [ -e $FILE ]then rm $FILE fi while [ $I -le $1 ]do echo "192.168.0.$I " >> $FILE I=$(expr $I + 1) done
1 2 3 4 5 6 7 8 9 10 11 12 mrcan@ubuntu:~$ ./generateIP.sh 10 mrcan@ubuntu:~$ cat IPs.txt 192.168.0.1 192.168.0.2 192.168.0.3 192.168.0.4 192.168.0.5 192.168.0.6 192.168.0.7 192.168.0.8 192.168.0.9 192.168.0.10
什么是IP地址的10进制?
不是简单的每个分段的10进制(192/168/0/1
这些)
而是要一个32位无符号整型数十进制表示的:从32个0到32个1的这之间的某一个数。
其实可以直接把IP地址看作32位2进制数 ,来转换、计算为其十进制值。
但是过程不太美观。
如以上过程,太复杂!
有可读性更好的方法:把IP地址看作4位256进制数 。来转换、计算为其十进制值。
IPv4地址每一个分段的数的大小范围:0到255,因此现在形式下的IPv4每位数属于256进制,是用.
分开了。
如上,很香!
那么我们平时说的“一百二十三”,这个3位数,就可以看作:1.2.3,每一个分段的大小范围是0到9,属于10进制。怎么计算其10进制数?power是10,
\begin{align}
1 * 10 ^ 2 &= 100 \\
2 * 10 ^ 1 &= 20 \\
3 * 10 ^ 0 &= 3 \\
100 + 20 + 3 &= 123
\end{align}
那么,4位256进制,转为10进制的算法:
\begin{align}
192 * 256 ^ 3 &= a\\
168 * 256 ^ 2 &= b\\
0 * 256 ^ 1 &= c\\
1 * 256 ^ 0 &= d\\
result &= a + b + c + d
\end{align}
编写转换进制
需要切分IP地址的4段。
用到了cut:见《[[Linux_cut命令]]》
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #! /bin/sh INPUT=IPs.txt OUTPUT=IPint.txt SUM=0 if [ -e $OUTPUT ]then rm $OUTPUT fi cat $INPUT | while read IPdo I=1 SUM=0 while [ $I -le 4 ] do NUM=$(echo $IP | cut -d. -f$I ) case $I in 1) SUM=$(expr $NUM \* 256 \* 256 \* 256) ;; 2) SUM=$(expr $SUM + $NUM \* 256 \* 256) ;; 3) SUM=$(expr $SUM + $NUM \* 256) ;; 4) SUM=$(expr $SUM + $NUM ) ;; esac I=$(expr $I + 1) done echo $IP $SUM >> $OUTPUT done
输出结果:
1 2 3 4 5 6 7 8 9 10 192.168.0.1 3232235521 192.168.0.2 3232235522 192.168.0.3 3232235523 192.168.0.4 3232235524 192.168.0.5 3232235525 192.168.0.6 3232235526 192.168.0.7 3232235527 192.168.0.8 3232235528 192.168.0.9 3232235529 192.168.0.10 3232235530
注意点
没有+=
这个形式
expr
的设计中没有包含幂运算符(如 **
或 ^
),因此只能用num \* 256 \* 256 \* 256
这样连乘。
case结束别忘记esac
知识点
程序的精髓在于,怎么循环read读取一个文件中的每一行,我们用cat $INPUT |
管道传入read,再给read加一层循环,这样,只要管道中还有内容,while循环就不会停止。
函数
三个问题:1、如何传参的问题;2、函数返回值如何获得;3、如何看待函数内定义的变量
特点
shell脚本中的函数没有声明,需要直接定义在最前面。
调用函数不用加圆括号,而是只有函数名
1 2 3 4 5 fun (){ echo "fun run" } fun
参数
注意$#/$1/$2
在函数中、函数外的区别:在函数中代表函数的参数;而在函数外代表此shell脚本的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 fun (){ echo "fun run" echo "fun: \$#=$# " echo "fun: \$1=$1 " echo "fun: \$2=$2 " } fun hello 123 echo "my.sh: \$#=$# " echo "my.sh: \$1=$1 " echo "my.sh: \$2=$2 "
返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 my_add (){ if [ "$# " -ne 2 ] then echo "参数有误" return 0 fi res=`expr $1 + $2 ` return $res } res=my_add echo "$res "
变量生存期问题
函数中的变量可能污染到函数外
1 2 3 4 5 6 7 8 9 10 11 my_test (){ str=hello echo "my_test:str=$str " } my_test echo "str=$str "
此问题的原因:
脚本程序的概念并不存在作用域的概念。所以,函数中定义、赋值str语句执行 后,不管是函数中,还是bash解释器,也就都存在str这个变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 my_test (){ str=hello echo "my_test:str=$str " } test2 (){ echo "test2:str=$str " } my_test echo "str=$str " test2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 my_test (){ str=hello echo "my_test:str=$str " } test2 (){ echo "test2:str=$str " } echo "str=$str " test2 my_test
解决变量污染的方法
1、可以通过unset在用完变量后,销毁之。
1 2 3 4 5 6 7 8 9 10 11 my_test (){ str=hello echo "my_test:str=$str " unset str } my_test echo "str=$str "
2、可以在变量名前加local
1 2 3 4 5 6 7 8 9 10 11 str="abcdef" my_test (){ local str=hello echo "my_test:str=$str " } my_test echo "str=$str "
注意事项:unset对于local变量,只会局部地销毁这个变量,不影响全局的同名变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 str="abcdef" my_test (){ local str=hello echo "my_test:str=$str " unset str } my_test echo "str=$str " str="abcdef" my_test (){ str=hello echo "my_test:str=$str " unset str } my_test echo "str=$str "
脚本间的调用
1 2 3 4 5 6 7 8 9 10 11 12 13 echo "b.sh run pid=$$" ./d.sh exit 0echo "d.sh run pid=$$" exit 0
脚本间变量的传递问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 echo "b.sh run pid=$$" mystr=hello echo "b.sh mystr=$mystr " ./d.sh exit 0echo "d.sh run pid=$$" echo "d.sh mystr=$mystr " exit 0
为了在脚本间可以传递变量,可有以下方式
1、在一个sh中调用另一个sh时,后面加参数。但是只是能拿到值,变量名字不能通用,需要用“$1”来取出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 echo "b.sh run pid=$$" mystr=hello echo "b.sh mystr=$mystr " ./d.sh $mystr exit 0echo "d.sh run pid=$$" echo "d.sh mystr=$1 " exit 0
2、如果想要变量名字通用,可以export改变mystr为环境变量,因为环境变量可以被继承,所以让变量名字通用了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 echo "b.sh run pid=$$" mystr=hello export mystr echo "b.sh mystr=$mystr " ./d.sh exit 0echo "d.sh run pid=$$" echo "d.sh mystr=$mystr " exit 0
3、通过source。source的作用是在调用另一个脚本时,不启动另外的sh,而是在自身的解释器中去运行另一个脚本的命令。相当于内联展开于此,所以原来的变量可以复用。但是问题的隐患很多,因为调用的脚本会对原来的脚本造就的环境造成影响。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 echo "b.sh run pid=$$" mystr=hello echo "b.sh mystr=$mystr " . ./d.sh exit 0echo "d.sh run pid=$$" echo "d.sh mystr=$mystr " exit 0
source的作用:有时需要对当前的bash进行环境变量的初始化,可以用source(. ./xx.sh)来在本sh直接运行提前写好的配置脚本。
C程序和脚本间的调用
脚本调用C程序显而易见。
我们讨论C程序调用脚本。
1 2 3 4 5 6 #!/usr/bin/bash echo "my.sh pid=$$" mystr="hello^_^" echo "mystr=$mystr " exit 0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main () { printf ("main pid=%d\n" ,getpid()); execl("./my.sh" ,"my.sh" ,(char *)0 ); printf ("execl err\n" ); exit (0 ); }
表面上启动的是my.sh ,实际上启动的是/usr/bin/bash。
练习
从键盘读取一个成绩,0~100之外的不可以。把整数值转为等级A、B、C、D。
成绩
等级
>= 90
A
>= 80
B
>= 70
C
>= 60
D
< 60
E
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #!/usr/bin/bash while [ 1 ]; do printf 'please input your score:' read score if [ $score -ge 0 ] && [ $score -le 100 ] then grade=$(($score /10 )) printf 'your grade is:' case $grade in 9 | 10 ) echo "A" ;; 8 ) echo "B" ;; 7 ) echo "C" ;; 6 ) echo "D" ;; * ) echo "E" ;; esac else echo "error in your input!" fi done exit 0
测试结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #!/usr/bin/bash printf '输入你的成绩:' read scorecase $score in [0-9] | [1-9][0-9] | 100) grade=$(($score /10 )) printf "你的成绩是$score ,等级:" case $grade in 9 | 10 ) echo "优" ;; 8 ) echo "良" ;; 7 ) echo "中" ;; 6 ) echo "及格" ;; * ) echo "不及格" ;; esac ;; *) echo "error in your input!" ;; esac exit 0
循环接收用户输入的学生成绩(百分制),若成绩小于60,输出“不及格”;若成绩大于等于60,输出“及格”,按Q(q)键退出。
1 2 3 4 5 6 7 8 9 10 11 #!/bin/bash while true do read -p "输入成绩:" score case $score in [Qq]) exit ;; [0-9] | [1-5][0-9]) echo "不及格" ;; 100 | [6-9][0-9]) echo "及格" ;; *) echo "不合法,请输入0~100" ;; esac done
循环接收某门课程的成绩,计算用户已输入的最高分、最低分、平均分,按P(p)键输出计算结果,按Q(q)键退出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #!/bin/bash min=100 max=0 sum =0count=0 while true do read -p "输入分数:" score case $score in [0-9] | [1-9][0-9] | 100) echo "$score 已录入" sum =$(($sum +$score )) count=$(($count +1 )) if [ $score -gt $max ] then max=$score fi if [ $score -lt $min ] then min=$score fi ;; [Pp]) echo "max = $max " echo "min = $min " echo "avg = $(($sum/$count) )" continue ;; [Qq]) exit ;; *) echo "$score 不合法,请重新输入。" esac done
注意
例:输入一个正整数,判断其是否为质数。正确的程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/bin/bash read -p "请输入一个正整数:" number[ $number -eq 1 ] && echo "$number 不是质数" && exit for i in `seq 2 $(($number -1 ))`do [ $[$number % $i ] -eq 0 ] && echo "$number 不是质数" $$ exit done echo "$number 是质数" && exit
其中,判断语句如[ $number -eq 1 ]必须在首尾中括号之间有空格,不然找不到命令。如下是错误的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 read -p "请输入一个正整数:" number[$number -eq 1] && echo "$number 不是质数" && exit for i in `seq 2 $(($number -1 ))`do [$[$number % $i ] -eq 0] && echo "$number 不是质数" $$ exit done echo "$number 是质数" && exit