pdf版本笔记的下载地址: Shell脚本编程入门(访问密码:3834)
Shell脚本编程入门
Shell是一个面向字符串编程的脚本语言.有两个很好的入门Shell教程: Shell 教程-菜鸟教程, Shell编程-C语言中文网.
一个shell脚本的示例
创建一个脚本的过程分为3步:
-
创建一个Shell脚本文件
hello.sh
(建议脚本文件以.sh
结尾),内容如下:#!/bin/bash echo "Hello World !"
第一句的
#!
符号定义了执行脚本的解释器为/bin/bash
. -
赋予用户执行该脚本的权限:
chmod u+x hello.sh # 赋予拥有者执行该脚本的权限
-
执行该脚本
./hello.sh
可以看到屏幕输出
Hello World !
.在执行脚本时,一定要加上
./
,表示执行当前目录下的hello.sh
文件;否则系统会到PATH
路径下寻找名为hello.sh
的脚本并执行.
变量
自定义变量
定义和使用变量
在Shell脚本中,使用变量名=值
定义变量(在定义变量时等号=
左右两边不能有空格),使用$变量名
或${变量名}
引用变量.
url1=http://www.baidu.com
echo $url1 # 输出 http://www.baidu.com
url2='http://www.baidu.com'
echo $url2 # 输出 http://www.baidu.com
url3="http://www.baidu.com"
echo $url3 # 输出 http://www.baidu.com
在Shell中,除数字以外所有的变量默认都被视为字符串,不论是否显示地使用引号(""
或''
)包围变量的值.其中单引号'
或"
是不同的:
- 以单引号
''
包围的字符串,不会解析其中的变量和命令,字符串中的任何内容都会原样输出. - 以双引号
""
包围的字符串,会先解析其中的变量和命令,将执行结果替换到字符串内容中.
#!/bin/bash
name="chenhai"
echo 'my name is ${name}' # 输出 my name is ${name}
echo "my name is ${name}" # 输出 my name is chenhai
echo my name is ${name} # 输出 my name is chenhai
echo 'today is `date`' # 输出 today is `date`
echo "today is `date`" # 输出 today is Sun Dec 8 06:Sun Dec 8 06:11:11 UTC 2019`date`
只读变量
使用readonly
关键字可以将变量定义为只读变量,只读变量的值不能被改变.
#!/bin/bash
url="http://www.google.com"
readonly url
url="http://www.baidu.com" # 修改只读变量的值会报错: This variable is read only.
执行上述程序,结果如下:
/bin/sh: NAME: This variable is read only.
删除变量
使用unset
命令可以删除变量.
#!/bin/bash
url="http://www.google.com"
unset url
echo ${url}
运行上述脚本没有输出.
定义和使用数组
Shell数组不要求数组中元素类型均相同,有两种方式定义数组:
-
整体定义:
array_name=(value0 value1 ... valuen)
-
单独定义
array_name[0]=value0 array_name[1]=value1 ... array_name[n]=valuen
使用${array_name[n]}
可以引用数组array_name
的第n
个元素,使用@
或*
占位符索引数组表示取数组的所有元素.
#!/bin/bash
nums=(29 100 'www.baidu.com')
echo ${nums[@]} # 输出 29 100 www.baidu.com
nums[10]=66 # 给第10个元素赋值(此时会增加数组长度)
echo ${nums[*]} # 输出 29 100 www.baidu.com 66
echo ${nums[2]} # 输出 www.baidu.com
echo ${nums[4]} # 输出 空行
特殊变量
接收命令行参数的位置变量
位置变量常用于接收从命令行传递过来的参数.$0
表示脚本文件名,$<num>
表示第num
个传入的参数.
下面创建一个脚本test.sh
,内容如下:
echo "script name is $0"
echo "argument 1 is $1"
echo "argument 2 is $2"
echo "argument 3 is $3"
赋予当前用户执行该脚本的权限并在命令行中调用该脚本并传入参数:
./test.sh 192.168.0.1 8080 "hello world"
得到输出如下:
script name is 192.168.0.1
argument 1 is 192.168.0.1
argument 2 is 8080
argument 3 is hello world
其它特殊变量
变量 | 含义 |
---|---|
$0 | 当前脚本文件名 |
$num | 传递给脚本的第num 个参数 |
$# | 传递给脚本的参数个数 |
$* | 传递给脚本的所有参数(将所有的参数视为一个单词) |
$@ | 传递给脚本的所有参数(将所有的参数视为多个单词) |
$? | 上一个命令的退出状态.0表示执行成功,其他值表示执行失败 |
$$ | 当前脚本的进程号 |
下面例子展示$*
和$@
的区别:
创建脚本test.sh
,内容如下:
#!/bin/bash
echo "共传递给脚本$#个参数"
echo '演示使用$*遍历参数: 所有参数被视为一个单词'
for i in "$*"; do
echo $i
done
echo '演示使用$@遍历参数: 所有参数被视为多个单词'
for i in "$@"; do
echo $i
done
赋予当前用户执行该脚本的权限并在命令行中调用该脚本并传入参数:
./test aa bb 33 44
得到输出如下:
共传递给脚本4个参数
演示使用$*遍历参数: 所有参数被视为一个单词
aa bb 33 44
演示使用$@遍历参数: 所有参数被视为多个单词
aa
bb
33
44
命令替换: 将命令的输出结果赋值给变量
命令替换是指将命令的输出结果赋给某个变量,有两种形式可以完成命令替换,一种是反引号`
,另一种是$()
.
variable=`commands`
variable=$(commands)
使用命令替换时最好将变量用双引号""
包围,否则在多行输出时易发生混乱:
#!/bin/bash
LSL=`ls -l`
echo $(ls -l) # 不使用双引号包围变量
echo "--------" # 输出分隔符
echo "$(ls -l)" # 使用引号包围变量
运行上述程序,得到输出如下:
total 8 drwxr-xr-x. 2 root root 21 7月 1 2016 abc -rw-rw-r--. 1 chenhai chenhai 147 10月 31 10:29 demo.sh -rw-rw-r--. 1 chenhai chenhai 35 10月 31 10:20 demo.sh~
--------
total 8
drwxr-xr-x. 2 root root 21 7月 1 2016 abc
-rw-rw-r--. 1 chenhai chenhai 147 10月 31 10:29 demo.sh
-rw-rw-r--. 1 chenhai chenhai 35 10月 31 10:20 demo.sh~
反引号`
和$()
在大多数情况下是等价的,但是$()
支持多层嵌套,且可读性更好.但$()
仅在Bash Shell中有效.
输入输出
标准输入输出
read命令
使用read
命令从标准输入读取一段内容并将其赋值给变量.read
命令的用法为:
read [-options] [variables]
variables
表示存储数据的变量,options
表示选项,常用选项如下:
选项 | 说明 |
---|---|
-a <array> | 把读取的数据赋值给数组array |
-d <delimiter> | 用字符串delimiter 指定读取结束的位置,而不是一个换行符(读取到的数据不包括 delimiter |
-e | 在获取用户输入的时候,对功能键进行编码转换,不会直接显式功能键对应的字符 |
-n <num> | 读取num 个字符,而不是整行字符 |
-p <prompt> | 显示提示信息,提示内容为prompt |
-r | 原样读取(Raw mode),不把反斜杠字符解释为转义字符 |
-s | 静默模式(Silent mode),不会在屏幕上显示输入的字符.当输入密码和其它确认信息的时候,这是很有必要的 |
-t <seconds> | 设置超时时间,单位为秒.如果用户没有在指定时间内输入完成,那么 read 将会返回一个非 0 的退出状态,表示读取失败. |
-u <fd> | 使用文件描述符fd 作为输入源,而不是标准输入,类似于重定向 |
下面程序要求使用者在20秒内输入密码:
#!/bin/bash
if
read -t 20 -sp "Enter password in 20 seconds(once) > " pass1 && printf "\n" && #第一次输入密码
read -t 20 -sp "Enter password in 20 seconds(again)> " pass2 && printf "\n" && #第二次输入密码
[[ $pass1 == $pass2 ]] #判断两次输入的密码是否相等
then
echo "Valid password"
else
echo "Invalid password"
fi
在这里使用&&
组合命令,若其中一条不满足,则退出.
echo命令
使用echo
输出时默认会换行,可以使用-n
参数使命令不换行.
使用echo
输出时默认不对反斜杠\
开头的字符进行转义,可以使用-e
参数开启转义.开启转义后也可以使用\c
字符控制不换行.
printf命令
printf
命令模仿C语言中的printf()
函数,其用法如下:
printf format-string [arguments...]
其中的format-string
与C语言中的格式控制字符串大同小异.
#!/bin/bash
# format-string为双引号
printf "%d %s\n" 1 "abc"
# 单引号与双引号效果一样
printf '%d %s\n' 1 "abc"
# 没有引号也可以输出
printf %s abcdef
# 格式只指定了一个参数,但多出的参数仍然会按照该格式输出,format-string 被重用
printf %s abc def
printf "%s\n" abc def
printf "%s %s %s\n" a b c d e f g h i j
# 如果没有 arguments,那么 %s 用NULL代替,%d 用 0 代替
printf "%s and %d \n"
输入输出重定向
文件描述符
在Linux系统中,一切皆文件,输入输出设备都是以文件的形式被对待的,Linux命令运行时会打开3个文件.
文件描述符 | 文件名 | 类型 | 硬件 |
---|---|---|---|
0 | stdin | 标准输入文件 | 键盘 |
1 | stdout | 标准输出文件 | 显示器 |
2 | stderr | 标准错误输出文件 | 显示器 |
输出重定向
可以使用>
(覆盖)或>>
(追加)将标准输出重定向到文件中,可以通过给出文件描述符指定将标准输出文件或标准错误输出文件重定向到文件中(默认文件描述符为1,只重定向标准输出).
类型 | 命令 | 作用 |
---|---|---|
标准输出重定向 | command >file | 以覆盖的方式,将command 的正确结果输出到文件file 中 |
标准输出重定向 | command >>file | 以追加的方式,将command 的正确结果输出到文件file 中 |
标准错误输出重定向 | command 2>file | 以覆盖的方式,将command 的错误结果输出到文件file 中 |
标准错误输出重定向 | command 2>>file | 以追加的方式,将command 的错误结果输出到文件file 中 |
标准输出和标准错误输出同时重定向 | command &>file 或 command >&file 或 command >file 2>&1 或 command >file 2&>1 | 以覆盖的方式,将command 的正确结果和错误结果都输出到文件file 中 |
标准输出和标准错误输出同时重定向 | command 1>file1 2>file2 | 以覆盖的方式,将command 的正确结果输出到文件file1 中,错误结果输出到文件file2 中 |
标准输出和标准错误输出同时重定向 | command &>>file | 以追加的方式,将command 的正确结果和错误结果都输出到文件file 中 |
标准输出和标准错误输出同时重定向 | command 1>>file1 2>>file2 | 以追加的方式,将command 的正确结果输出到文件file1 中,错误结果输出到文件file2 中 |
输入重定向
不再使用键盘作为命令输入的来源,而是使用文件作为命令的输入.
符号 | 说明 |
---|---|
command <file | 将file 文件中的内容作为command 的输入 |
command <<END | 从标准输入(键盘)中读取数据,直到遇见分界符END 才停止(分界符可以是任意的字符串,用户自己定义) |
command <file1 >file2 | 将file1 作为command 的输入,并将command 的结果输出到file2 |
和输出重定向类似,输入重定向的完整写法是fd<file
,其中fd
表示文件描述符,如果不写,默认为0.
fd<file
下面程序中使用文件readme.txt
作为while
子句的输入:
while read str; do
echo $str
done <readme.txt
Shell运算符
Shell的运算符很复杂,有一些运算符或者命令存在着各种各样的缺点.因此,推荐使用的运算符如下:
- 进行算术运算时,使用运算符
(())
. - 在进行逻辑运算时,使用运算符
[[]]
.
算数运算
与其他编程语言不同,Shell不能直接进行算数运算,必须使用数学计算命令.下面是一个反例:
echo 2+8 # 输出 2+8
a=23
b=$a+55
echo $b # 输出 23+55
b=90
c=$a+$b
echo $c # 输出 23+90
Shell中常用的数学运算符有两个,一个是expr
,另一个是(( ))
或[]
,他们都只能进行整数运算.其中expr
比较繁琐,推荐使用(())
或[]
.要想进行小数运算,需要借助bc
命令.
Shell中支持的算术运算符如下:
算术运算符 | 说明/含义 |
---|---|
+ ,- | 加法(或正号)、减法(或负号) |
* ,/ ,% | 乘法、除法、取余(取模) |
** | 幂运算 |
++ ,-- | 自增和自减,可以放在变量的前面也可以放在变量的后面 |
! ,&& ,|| | 逻辑非(取反)、逻辑与(and)、逻辑或(or) |
< ,<= ,> ,>= ,!= | 比较符号(小于、小于等于、大于、大于等于) |
<< ,>> | 向左移位、向右移位 |
~ ,| ,& ,^ | 按位取反、按位或、按位与、按位异或 |
= ,+= ,-= ,*= ,/= ,%= | 赋值运算符 |
#!/bin/bash
a=10
b=20
c=30
echo '(a+b)=' $((a+b)) # 输出 (a+b)= 30
echo '(a-b)=' $((a-b)) # 输出 (a-b)= -10
echo '(a*b)=' $((a*b)) # 输出 (a*b)= 200
echo '(a/b)=' $((a/b)) # 输出 (a/b)= 0
echo '(a%b)=' $((a%b)) # 输出 (a%b)= 10
echo '(a**b)=' $((a*b)) # 输出 (a**b)= 7766279631452241920
echo '(~a)=' $((~a)) # 输出 (~a)= -11
echo '(a==b)=' $((a=b)) # 输出 (a==b)= 0
echo '(a>b)=' $((a>b)) # 输出 (a>b)= 0
echo '(a<b && b<c)=' $((a>b && b<c)) # 输出 (a<b && b<c)= 0
((c*=2))
echo 'c=' $c # 输出 c= 60
关系运算符
使用test
命令可以进行关系运算,test
命令可以测试三类对象: 字符串,整数,文件属性.测试结果为真时返回0
,为假时返回1
.
test
命令也可以简写为[]
,或使用[[]]
关键字替代,使用[]
或[[]]
时必须在括号与表达式之间加空格.下面两种写法是等价的:
test expression
[ expression ] # 使用[]时必须在括号与表达式之间加空格
[[ expression ]] # 使用[[]]时必须在括号与表达式之间加空格
test
命令(或简写为[]
)本质上是命令,为避免空参数的问题,应尽量将参数用双引号""
包围起来,也需要注意转义的问题(如>
应写为\>
).而[[]]
是Shell内置的关键字,不需要担心空参数和转义的问题.因此,在进行关系运算时,应尽量使用[[]]
.
数值比较
运算符 | 说明 |
---|---|
$num1 -eq $num2 | 判断 $num1 和$num2 是否相等 |
$num1 -ne $num2 | 判断 $num1 和$num2 是否不相等 |
$num1 -gt $num2 | 判断 $num1 是否大于$num2 |
$num1 -lt $num2 | 判断 $num1 是否小于$num2 |
$num1 -ge $num2 | 判断 $num1 是否大于等于$num2 |
$num1 -le $num2 | 判断 $num1 是否小于等于$num2 |
#!/bin/bash
num1=10
num2=20
[[ $num1 -eq $num2 ]]; echo $? # 输出 1
[[ $num1 -ne $num2 ]]; echo $? # 输出 0
[[ $num1 -gt $num2 ]]; echo $? # 输出 1
[[ $num1 -lt $num2 ]]; echo $? # 输出 0
[[ $num1 -ge $num2 ]]; echo $? # 输出 1
[[ $num1 -le $num ]]; echo $? # 输出 0
字符串比较
运算符 | 说明 |
---|---|
$str1 = $str2 或$str1 == $str2 | 判断str1 和str2 是否完全相同 |
$str1 != $str2 | 判断str1 和str2 是否不同 |
-z $str | 判断str 的长度是否为0 |
-n $str | 判断str 的长度是否不为0 |
$str1 > $str2 | 判断str1 是否大于str2 |
$str1 < $str2 | 判断str1 是否小于str2 |
str1="aaa"
str2="bbbb"
[[ $str1 = $str2 ]]; echo $? # 输出 1
[[ $str1 == $str2 ]]; echo $? # 输出 1
[[ $str1 != $str2 ]]; echo $? # 输出 0
[[ -z $str1 ]]; echo $? # 输出 1
[[ -n $str1 ]]; echo $? # 输出 0
[[ $str1 > $str2 ]]; echo $? # 输出 1
[[ $str1 < $str2 ]]; echo $? # 输出 0
文件比较
-
文件类型判断
运算符 说明 -e filename
判断文件 filename
是否存在.-b filename
判断文件 filename
是否存在,并且是否为块设备文件.-c filename
判断文件 filename
是否存在,并且是否为字符设备文件.-d filename
判断文件 filename
是否存在,并且是否为目录文件.-f filename
判断文件 filename
是否存在,井且是否为普通文件.-L filename
判断文件 filename
是否存在,并且是否为符号链接文件.-p filename
判断文件 filename
是否存在,并且是否为管道文件.-s filename
判断文件 filename
是否存在,并且是否为非空.-S filename
判断文件 filename
是否存在,并且是否为套接字文件. -
文件权限判断
运算符 说明 -r filename
判断文件 filename
是否存在,并且是否拥有读权限.-w filename
判断文件 filename
是否存在,并且是否拥有写权限.-x filename
判断文件 filename
是否存在,并且是否拥有执行权限.-u filename
判断文件 filename
是否存在,并且是否拥有 SUID 权限.-g filename
判断文件 filename
是否存在,并且是否拥有 SGID 权限.-k filename
判断文件 filename
是否存在,并且是否拥有 SBIT 权限. -
文件比较
运算符 说明 filename1 -nt filename2
判断 filename1
的修改时间是否比filename2
新filename -ot filename2
判断 filename1
的修改时间是否比filename2
旧filename1 -ef filename2
判断 filename1
是否和filename2
的inode
号一致,即判断两者是否为同一文件
是一个判断硬链接的方法
逻辑运算
Shell还提供了逻辑运算符用于对比较结果进行逻辑运算.
运算符 | 说明 |
---|---|
-a | 与 |
-o | 或 |
! | 非 |
对于[[]]
运算符,不支持-a
与-o
,而应该使用&&
和||
.
流程控制语句
判断语句
if语句
if
语句有如下3种形式:
-
if
语句if condition then statement(s) fi
也可以将
if
与then
写在一行:if condition; then statement(s) fi
-
if-else
语句if condition; then statement1 else statement2 fi
-
if-elif-else
语句if condition1; then statement1 elif condition2; then statement2 elif condition3; then statement3 else statementn fi
下面例子中的脚本接受一个路径并判断它代表的是文件还是目录:
#!/bin/bash
if [[ $# -ne 1 ]]; then
echo "调用该命令的格式为: $0 <filename>"
fi
if [[ -f $1 ]]; then
echo "路径$1是一个文件"
elif [[ -d $1 ]]; then
echo "路径$1是一个目录"
else
echo "路径$1是其他格式"
fi
case语句
case语句的语法如下:
case expression in
pattern1)
statement1
;;
pattern2)
statement2
;;
pattern3)
statement3
;; # 相当于break
*) # 相当于default
statementn
esac
其中pattern
可以是数字,字符串或正则表达式.
其实最后的
*)
能用于匹配默认情况并不是什么语法规定,而是因为*
在正则表达式中本来就能表示任意字符.
下面脚本接受一个字符并判断它的类别:
#!/bin/bash
if [[ $# -ne 1 ]]; then
echo "调用该命令的格式为: $0 <character>"
fi
case $1 in
[a-zA-Z])
echo "$1是一个字符"
;;
[0-9])
echo "$1是一个数字"
;;
[,.?!])
echo "$1是一个符号"
;;
*)
echo "$1是其它字符"
esac
循环语句
while语句
while语句的语法如下:
while condition
do
statements
done
下面脚本用于创建多个文件:
#!/bin/bash
i=0
while ((i<10))
do
touch "file$i"
((i++))
done
for语句
可以使用C风格的for循环语句或Python风格的for循环语句,语法分别如下:
-
C风格的for循环语句
for ((exp1; exp2; exp3)) do statements done
下面脚本计算从1到100的加和:
#!/bin/bash sum=0 for ((i=1; i<=100; i++)) do ((sum += i)) done echo "The sum is: $sum"
-
Python风格的for循环语句
for variable in value_list do statements done
value_list
的取值可以有以下几种情况:-
直接给出的具体的值,多个值之间用空格分隔
#!/bin/bash for n in 1 2 3 4 5 6 do echo $n done
-
使用
{start..end}
给出的一个范围下面程序遍历1~100直接的整数
#!/bin/bash for n in {1..100} do echo $n done
下面程序遍历ASCII表上A~z间的所有字符
#!/bin/bash for c in {A..z} do echo $c done
输出
ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_\`abcdefghijklmnopqrstuvwxyz
-
其他命令的输出结果
下面程序遍历一个使用
seq
命令生成的等差数列#!/bin/bash for n in $(seq 1 2 20) do echo $n done
下面程序遍历当前目录下所有
.sh
文件#!/bin/bash for filename in $(ls *.sh) do echo $filename done
-
使用通配符
使用通配符,可以更方便的遍历当前目录下所有
.sh
文件#!/bin/bash for filename in *.sh do echo $filename done
-
使用特殊变量: 可以使用
$@
等特殊变量,事实上,若我们不显示指定value_list
,我们便利的也是$@
.#!/bin/bash function func(){ for str in $@ do echo $str done } func C++ Java Python C#
-
break和continue语句
与C语言不同的地方之处在于,break
和continue
语句都带有一个参数n
,表示退出循环的层数,默认为1
.
函数
函数的定义与调用
在Shell中,定义函数的语法如下:
[function] funname[()] {
statements
[return value]
}
可以像使用命令那样调用函数并传入参数
funname param1 param2 param3
与命令类似,在函数中使用$#
得到参数个数,使用$*
或$@
得到所有参数,使用$<num>
引用第num
个参数,使用#?
得到上一行被调用的函数的返回值.
#!/bin/bash
funWithParam(){
echo "参数总数有 $# 个!"
echo "第一个参数为 $1"
echo "第二个参数为 $2"
echo "第十个参数为 $10"
echo "第十个参数为 ${10}"
echo "第十一个参数为 ${11}"
echo "作为一个字符串输出所有参数 $* !"
return 1
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
echo $?
pdf版本笔记的下载地址: Shell脚本编程入门(访问密码:3834)