shell脚本入门
shell
这里说的shell表示shell脚本。
shell是和php、python这类一样的解释性语言。在Linux中shell的种类有:
- Bourne Shell(/usr/bin/sh或/bin/sh)
- Bourne Again Shell(/bin/bash)
- C Shell(/usr/bin/csh)
- K Shell(/usr/bin/ksh)
- Shell for Root(/sbin/sh)
- ……
通常默认的是bash,所以之后都是在bash下执行的。
第一个shell脚本
创建一个hello.sh,然后编辑一下内容,
#!/bin/bash
echo "Hello World!!"
这个程序只有两行,第一行告诉系统解释此脚本的shell程序;第二行是打印内容。
执行这个程序有两种方法:
作为可执行程序
chmod +x hello.sh
./hello.sh
首先给程序赋予执行权限,然后执行脚本。
在Linux中执行二进制程序需要./hello.sh,这是告诉系统在当前目录中寻找并执行该程序;如果使用hello.sh,系统会在PATH中寻找程序。
作为解释器参数
/bin/bash hello.sh
直接运行解释器。这样就不用在程序中指定解释器了。
注释
shell中的注释只有#,进行单行注释。
如果想要多行注释有两种方法:
-
将内容定义为一个函数,且不进行调用;
-
或者
:<<EOF xxxxx yyyyy zzzzz EOF
输出
echo
shell中的echo用于字符串输出,默认换行。
printf
printf模仿C程序中的printf程序,移植性比echo会更好。
printf可以格式化输出,不过不会自动换行。
一些实例
printf "Hello "
printf "World!\n" # 不会自动换行
printf "%10s %8s %4.2f\n" WWW female 46.35426 # 格式化输出
printf '%d %s\n' 123 "dcba" # 单引号和双引号效果一样
printf "%s" aaa bbb ccc # 多出的参数同样会输出
printf '\n'
printf "%s\n" ddd eee fff # 多出的参数会格式输出
printf "%s and %d and %f" # 如果不指定会有默认值
# output:
# Hello World!
# WWW female 46.35
# 1234 dcba
# aaabbbccc
# ddd
# eee
# fff
# and 0 and 0.000000
格式化字符串有:
%d--- 十进制整数%s--- 字符串%c--- 字符%f--- 浮点%e--- 科学计数法输出%o--- 无符号八进制%x--- 无符号十六进制(形式为fff)%X--- 无符号十六进制(形式为FFF)%#X--- 无符号十六进制(形式为0xFFF)
对应的位置必须是相应的格式,否则会报错。格式化输出可以进行修饰:
-:对齐方式;默认右对齐,有-左对齐;0:补零;m.n:m表示长度,包括整数和小数部分;n值精度,即小数位数;
具体的可以参考:printf格式化字符输出对应解释 - biblog - 博客园 (cnblogs.com)
变量
在shell运行中,会同时存在三种变量:
- 局部变量:局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell程序不能访问局部变量;
- 环境变量:所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本可以定义环境变量。
- shell变量:shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行。
shell中定义变量
var=123
str="hello"
一定要注意变量名和等号之间不能有空格。同时变量名只能使用数字字母下划线,首个字符不能以数字开头。
使用变量时需要加上$符号,为了区分变量的边界最好加上{}花括号,
var=123
str="Hello"
echo "> The number is ${var}"
echo "> Say ${str}"
# output:
# > The number is 123
# > Say Hello
#
如果要删除一个变量可以使用unset,
unset var
变量被删除后不能再次使用。
在shell中还有一种只读变量,使用readonly定义,
x=123
readonly x
只读变量被定义后不能被修改。
unset不能删除只读变量。
2022年3月7日-补充
在文件夹下有文件a.sh,里面内容为:
#!/bin/bash
var="*.sh"
echo $var
echo "$var"
echo ${var}
运行结果是什么?
a.sh
*.sh
a.sh
发现$var和${var}都被当成命令执行了,所以变量使用时最好加上双引号。⚠️
字符串
字符串定义可以使用单引号''或者双引号"",两者的区别是单引号中的任何内容会原样输出。
var=123
echo '> number is ${var}'
echo "> number is ${var}"
# output:
# > number is ${var}
# > number is 123
#
字符串拼接
拼接字符串可直接s1s2s3这样,
s1="AAA"
s2="BBB"
echo ${s1}${s2}
echo ${s1} ${s2}
echo ${s1}" "${s2}
echo ${s1}"---"${s2}
# output:
# AAABBB
# AAA BBB
# AAA BBB
# AAA---BBB
#
需要注意第4行的输出,中间有多个空格时,只输出了一个。
字符串操作很多,另起文章说明,这里就不过多介绍了。
数组
bash支持一维数组,并且没有限定数组的大小。bash中的数组与C语言的类似,下标由0开始编号,获取数组的元素要利用下标,下标可以是整数或者算术表达式,其值应该大于等于0.
定义一个数组
array=(12 324 1234)
echo "array: ${array[@]}" # 输出所有元素
echo "array[0]: ${array[0]}" # 输出第一个元素
echo "array length: ${#array[@]}" # 输出元素个数
echo "array[0] length: ${#array[0]}" # 输出第一个元素长度
# output:
# array: 12 324 1234
# array[0]: 12
# array length: 3
# array[0] length: 2
#
参数
shell脚本中可以使用$n来获取参数,n为数字,代表第几个参数。
echo "File name: ${0}"
echo "First parameter: ${1}"
echo "Second parameter: ${2}"
echo "The number of parameters: ${#}"
echo "The all parameters: ${*}"
当运行的时候指定参数,
# ./ch1.sh www yyy
File name: ./ch1.sh
First parameter: www
Second parameter: yyy
The number of parameters: 2
The all parameters: www yyy
一些特殊字符用来处理参数:
| 参数处理 | 说明 |
|---|---|
$# |
参数个数 |
$* |
所有参数 |
$@ |
所有参数 |
$! |
后台运行的最有一个进程的ID |
| `$ | 参数处理 |
| -------- | ------------------------------------------------------- |
| 脚本运行时的当前进程ID |
| $- | 显示Shell使用的当前选项,与set命令相同 |
| $? | 显示最后命令的退出状态。0表示正常,其他任何值都表示错误 |
$*和$@的区别
$*和$@都是引用所有参数,但是在双引号中,$*将参数当成一个整体,而$@会将参数分开。
echo "-- \$* ---"
for i in "$*"; do
echo $i
done
echo "-- \$@ ---"
for i in "$@"; do
echo $i
done
执行脚本,
# ./ch1.sh www yyy
-- $* --
www yyy
-- $@ --
www
yyy
其他的说明
如果传递的参数包含空格,应该使用单引号或双引号将参数括起来,以便于脚本将这个参数作为整体接受。
运算符
算数运算符
原生bash不支持简单的数学运算,需要通过其他命令实现,最常用的是expr。
res = `expr 2 + 2`
echo "2 + 2 = ${res}"
需要注意两点:
- 表达式和运算符之间要有空格
- 完整的表达式要用反引号括住
补充:
- 相加的另一种方法:$(($a+$b))
- 还有一种方法:$[a+b]
- 看到有人推荐使用 $(expr 2 + 2)
常用的算数运算符及其使用:
| 运算符 | 说明 | 举例 |
|---|---|---|
+ |
加法 | `expr $a + $b` |
- |
减法 | `expr $a - $b` |
* |
乘法 | `expr $a \* $b` |
/ |
除法 | `expr $a / $b` |
% |
取余 | `expr $a \% $b` |
= |
赋值 | a=$b |
== |
相等 | [ $a == $b ] |
!= |
不相等 | [ $a != $b ] |
需要注意的是乘法,表达式中需要转义。注意这里需要有空格的,但是怎么都显示不了。。。(不知道cnblogs怎么回事)
a=10
b=20
val=`expr $a + $b`
echo "a + b : $val"
val=`expr $a - $b`
echo "a - b : $val"
val=`expr $a \* $b`
echo "a * b : $val"
val=`expr $b / $a`
echo "b / a : $val"
val=`expr $b % $a`
echo "b % a : $val"
# output:
# a + b : 30
# a - b : -10
# a * b : 200
# b / a : 2
# b % a : 0
#
关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
| 运算符 | 说明 | 举例 |
|---|---|---|
-eq |
== |
[ $a -eq $b ] |
-ne |
!= |
[ $a -ne $b ] |
-gt |
> |
[ $a -gt $b ] |
-lt |
< |
[ $a -lt $b ] |
-ge |
>= |
[ $a -ge $b ] |
-le |
<= |
[ $a -le $b ] |
运行实例
a=10
b=20
if [ $a -eq $b ]
then
echo "$a -eq $b : a 等于 b"
else
echo "$a -eq $b: a 不等于 b"
fi
if [ $a -ne $b ]
then
echo "$a -ne $b: a 不等于 b"
else
echo "$a -ne $b : a 等于 b"
fi
if [ $a -gt $b ]
then
echo "$a -gt $b: a 大于 b"
else
echo "$a -gt $b: a 不大于 b"
fi
if [ $a -lt $b ]
then
echo "$a -lt $b: a 小于 b"
else
echo "$a -lt $b: a 不小于 b"
fi
if [ $a -ge $b ]
then
echo "$a -ge $b: a 大于或等于 b"
else
echo "$a -ge $b: a 小于 b"
fi
if [ $a -le $b ]
then
echo "$a -le $b: a 小于或等于 b"
else
echo "$a -le $b: a 大于 b"
fi
# output:
# 10 -eq 20: a 不等于 b
# 10 -ne 20: a 不等于 b
# 10 -gt 20: a 不大于 b
# 10 -lt 20: a 小于 b
# 10 -ge 20: a 小于 b
# 10 -le 20: a 小于或等于 b
#
布尔运算符
常用的布尔运算符
| 运算符 | 说明 | 举例 |
|---|---|---|
! |
非运算 | [ ! false ] |
-o |
或运算 | [ true -o false ] |
-a |
与运算 | [ true -a false ] |
一些实例
a=10
b=20
if [ $a != $b ]
then
echo "$a != $b : a 不等于 b"
else
echo "$a == $b: a 等于 b"
fi
if [ $a -lt 100 -a $b -gt 15 ]
then
echo "$a 小于 100 且 $b 大于 15 : 返回 true"
else
echo "$a 小于 100 且 $b 大于 15 : 返回 false"
fi
if [ $a -lt 100 -o $b -gt 100 ]
then
echo "$a 小于 100 或 $b 大于 100 : 返回 true"
else
echo "$a 小于 100 或 $b 大于 100 : 返回 false"
fi
if [ $a -lt 5 -o $b -gt 100 ]
then
echo "$a 小于 5 或 $b 大于 100 : 返回 true"
else
echo "$a 小于 5 或 $b 大于 100 : 返回 false"
fi
# output:
# 10 != 20 : a 不等于 b
# 10 小于 100 且 20 大于 15 : 返回 true
# 10 小于 100 或 20 大于 100 : 返回 true
# 10 小于 5 或 20 大于 100 : 返回 false
#
逻辑运算符
逻辑运算符
| 运算符 | 说明 | 举例 |
|---|---|---|
&& |
逻辑与AND | [[ $a -lt 100 && $b -gt 100 ]] |
|| |
逻辑或OR | [[ $a -eq 100 || $b -ge 100 ]] |
一些实例
a=10
b=20
if [[ $a -lt 100 && $b -gt 100 ]]
then
echo "返回 true"
else
echo "返回 false"
fi
if [[ $a -lt 100 || $b -gt 100 ]]
then
echo "返回 true"
else
echo "返回 false"
fi
# output:
# 返回 false
# 返回 true
#
这里使用了[[]],与[]是有一些差别的:
[]表达式中,常见的>,<需要加转义字符,表示字符串大小比较,以ASCII码位置比较。不能直接使用>,<,还有||和&&,需要使用-a和-o。[[]]运算符是[]的扩展,支持<和>而不用转义,还是以字符串大小比较。同时支持||和&&,不再使用-a和-o。
字符串运算符
字符串运算符
| 运算符 | 说明 | 举例 |
|---|---|---|
= |
检查字符串是否相等 | [ $a = $b ] |
!= |
检查字符串是否不等 | [ $a != $b ] |
-z |
检查字符串长度是否为0 | [ -z $a ] |
-n |
检查字符串长度是否不为0 | [-n "$a" ] |
| ` | 运算符 | 说明 |
| ------ | ----------------------- | -------------- |
\| 检查字符串是否为不为空 \| `[ $a]` |
一些实例
a="abc"
b="efg"
if [ $a = $b ]
then
echo "$a = $b : a 等于 b"
else
echo "$a = $b: a 不等于 b"
fi
if [ $a != $b ]
then
echo "$a != $b : a 不等于 b"
else
echo "$a != $b: a 等于 b"
fi
if [ -z $a ]
then
echo "-z $a : 字符串长度为 0"
else
echo "-z $a : 字符串长度不为 0"
fi
if [ -n "$a" ]
then
echo "-n $a : 字符串长度不为 0"
else
echo "-n $a : 字符串长度为 0"
fi
if [ $a ]
then
echo "$a : 字符串不为空"
else
echo "$a : 字符串为空"
fi
# output:
# abc = efg: a 不等于 b
# abc != efg : a 不等于 b
# -z abc : 字符串长度不为 0
# -n abc : 字符串长度不为 0
# abc : 字符串不为空
#
特别说明
[-n "$a" ],这里为什么要加双引号?
a=""
if [ -n $a ]
then
echo "-n $a : 字符串长度不为 0"
else
echo "-n $a : 字符串长度为 0"
fi
输出结果为:
-n : 字符串长度不为 0
从结果上看 -n $a 返回 true,这并正确,正确的做法是 $a 这里应该加上双引号,否则 -n $a 的结果永远是 true:
a=""
if [ -n "$a" ]
then
echo "-n $a : 字符串长度不为 0"
else
echo "-n $a : 字符串长度为 0"
fi
输出结果为:
-n : 字符串长度为 0
文件测试运算符
文件测试运算符用于检测Unix文件的各种属性。
| 操作符 | 说明 | 举例 |
|---|---|---|
-b |
是否是块文件 | [ -b $file ] |
-c |
是否是字符设备文件 | [ -c $file ] |
-d |
是否是目录 | [ -d $file ] |
-f |
是否是普通文件 | [ -f $file ] |
-g |
是否设置了SGID位 | [ -g $file ] |
-k |
是否设置了粘着位(Sticky Bit) | [ -k $file ] |
-p |
是否是有名管道 | [ -p $file ] |
-u |
是否设置了SUID位 | [ -u $file ] |
-r |
是否可读 | [ -r $file ] |
-w |
是否可写 | [ -w $file ] |
-x |
是否可执行 | [ -x $file ] |
-s |
是否为空 | [ -s $file ] |
-e |
是否存在 | [ -e $file ] |
-L |
是否是一个符号链接 | [ -L $file ] |
一些实例
file="/var/www/runoob/test.sh"
if [ -r $file ]
then
echo "文件可读"
else
echo "文件不可读"
fi
if [ -w $file ]
then
echo "文件可写"
else
echo "文件不可写"
fi
if [ -x $file ]
then
echo "文件可执行"
else
echo "文件不可执行"
fi
if [ -f $file ]
then
echo "文件为普通文件"
else
echo "文件为特殊文件"
fi
if [ -d $file ]
then
echo "文件是个目录"
else
echo "文件不是个目录"
fi
if [ -s $file ]
then
echo "文件不为空"
else
echo "文件为空"
fi
if [ -e $file ]
then
echo "文件存在"
else
echo "文件不存在"
fi
# output:
# 文件可读
# 文件可写
# 文件可执行
# 文件为普通文件
# 文件不是个目录
# 文件不为空
# 文件存在
#
test命令
test命令用于检查某个条件是否成立,可以进行数值、字符和文件三个方面的测试。
不过直接用[]或者[[]]也行。
几个实例
num1=100
num2=100
if test $[num1] -eq $[num2]
then
echo '两个数相等!'
else
echo '两个数不相等!'
fi
# output:
# 两个数相等!
#
#--------------------------
num1="ru1noob"
num2="runoob"
if test $num1 = $num2
then
echo '两个字符串相等!'
else
echo '两个字符串不相等!'
fi
# output:
# 两个字符串不相等!
#
#--------------------------
cd /bin
if test -e ./notFile -o -e ./bash
then
echo '至少有一个文件存在!'
else
echo '两个文件都不存在'
fi
# output:
# 至少有一个文件存在!
#
流程控制
条件
# 用法1
if condition
then
...
fi
#--------------------
# 用法2
if condition;then
...
fi
#--------------------
# 用法3
if condition
then
...
else
...
fi
#--------------------
# 用法4
if condition
then
...
elif condition
then
...
else
...
fi
case
case 值 in
模式1)
...
;;
模式2)
...
;;
*)
...
;;
esac
循环
for循环
for var in item1 item2 item3
do
...
done
while循环
while condition
do
...
done
until循环
很少用。
until condition
do
...
done
函数
函数的定义方式为:
[function] funname[()]{
...
[return]
}
[]中的内容表示可以省略。
实例
function demo1(){
echo "这是一个函数"
}
demo1
# output:
# 这是一个函数
#
如果有参数和返回值,
function demo2(){
a=$1
b=$2
echo "第一个参数:$1"
echo "第二个参数:$2"
echo "a + b: $(expr $a + $b)"
}
demo2 2 3
echo $?
# output
# 第一个参数:2
# 第二个参数:3
# a + b: 5
# 0
#
这里的$?输出为0,在shell中0表示正常,除此之外的任何值都是有问题。函数中最后执行的是echo,是不会有返回值的,所以执行正常返回为0,准确的说并不是返回值,而是最后一条指令的退出状态为0。
如果修改为
function demo2(){
a=$1
b=$2
echo "第一个参数:$1"
echo "第二个参数:$2"
echo "a + b: $(expr $a + $b)"
return $(expr $a + $b)
}
demo2 2 3
echo $?
# output
# 第一个参数:2
# 第二个参数:3
# a + b: 5
# 5
#
这样最后的指令为求和,返回的就是结果了。
输入输出重定向
大多数 UNIX 系统命令从终端接受输入并将所产生的输出发送回到终端。一个命令通常从标准输入读取输入,默认情况下,这就是终端。同样,通常将输出写入到标准输出,默认情况下,这也是终端。
重定向列表:
| 命令 | 说明 |
|---|---|
command > file |
输出重定向到file |
command < file |
输入重定向到file |
command >> file |
以追加的方式重定向到file |
n > file |
将文件描述符为n的文件重定向到file |
n >> file |
将文件描述符为n的文件以追加的方式重定向到file |
n >& m |
将输出文件m和n合并 |
n <& m |
将输入文件m和n合并 |
<< tag |
将开始标记tag和结束标记tag之间的内容作为输入 |
关于文件描述符:
0表示标准输入STDIN1表示标准输出STDOUT2表示标准错误输出STDERR
一些实例
$ wc -l users
$ wc -l < users
这两个命令结果是不同的,第一个会输出文件名,第二个不会,因为命令仅知道是从标准输入读取内容的。
$ command < infile > outfile
命令将输入重定向到infile,将输出重定向到outfile。
Here Document
没有具体翻译,是一种文本的格式,形式为
command << delimiter
document
delimiter
说明:
delimiter可以是任何的符号;- 结尾的
delimiter一定要顶格。
一些实例
$ wc -l << EOF
000
111
222
EOF
/dev/null
/dev/null是一个特殊的文件,写入到这里的内容都会被丢弃;如果尝试读取文件内容,那么什么都读不到。
如果不希望屏幕上显示输出结果,可以将输出重定向到/dev/null,
$ command > /dev/null
如果不希望显示错误信息,可以这样,
$ command 2>/dev/null
其他的说明
$ command > file 2>&1
$ command >> file 2>&1
这里&没有固定的意思。放在>后面的&,表示重定向的目标不是一个文件,而是一个文件描述符;换言之,2>1表示将stderr重定向到当前路径下文件名为1的regular_file中;而2>&1代表将stderr重定向到文件描述符为1的文件,即/dev/stdout中。
>&file和&>file的意思是完全相同的。
文件包含
shell可以包含外部脚本。语法格式为
. filename
source filename
被包含的文件不需要可执行权限。

浙公网安备 33010602011771号