Linux shell脚本基础知识
shell脚本
介绍
shell是一个命令解释器,它的作用是解释执行用户输入的命令或者程序
- 交互式的方式进行执行
- 非交互式的方式进行执行,通过shell脚本来执行
shell脚本的类型
shell脚本语言是弱类型语言,无需定义变量类型即可使用,Linux下主要有两种类型的shell语言
- Bourne shell 包括Bourne shell(sh)、Korn shell(ksh)、Bourne again shell
- C shell 包括csh, tcsh两种类型
查看系统默认的shell
echo $SHELL
查看系统支持的shell类型
cat /etc/shells
shell脚本执行
shell脚本的执行通常可以按照以下几种方式进行:
- bash filename / sh filename 这种方式适用于脚本文件本身没有执行权限的时候
- /path/filename 这种方式适用于脚本文件本身具有执行权限,则可以直接执行
- source filename / .filename 通过这种方式读入和加载shell脚本文件,然后依次执行shell脚本中的语句,有区别的是,这些语句是在当前父shell脚本的进程中执行。其他两种方法都会启用新的进程执行该脚本。
shell脚本编写规范
遵循以下shell脚本规范,在提高开发效率的同时,降低后期的维护成本
- 一个规范的shell脚本应该在第一行指出应该由哪个解释器来执行shell脚本中的内容,指定的方式为
#! /bin/bash
或者#! /bin/sh
, sh为bash的软连接,因此两者没有区别 - ‘#’后面的内容表示注释内容,注释内容不会被执行,也不会被解释器看到
- shell脚本的开头加版本,版权等信息
- shell脚本文件的扩展名为 .sh
- 成对的符号应尽量一次性写出来,然后退格在符号内增加内容,以防止遗漏。这些成对的符号包括: {}、[]、‘’、“” 等
- 中括号[]两端至少要有1个空格,因此,键入中括号时即留出空格[ ],然后在退格键入中间内容,并确保两端都至少由一个空格
- 对于流程控制语句,应一次性将格式写完,再添加内容
变量
-
变量的赋值方法为: 先写变量名称,紧接着是= ,最后是值。中间无任何空格。 通过echo命令加上 $变量名,即可输出变量的值
-
交互式赋值方法,将外部输入读取到变量中:
read -p "Please input option: " Opt echo The user input is: $Opt
-
赋值时使用引号
- 双引号 : 允许通过$符号引用其他变量的值
- 单引号 : 不允许使用$符号引用其变量的值
- 反引号 : 提取命令执行后的输出结果
- $() 与 ``含义相同
#! /bin/bash A=10 echo $A # 无引号 B=$A+10 echo $B # 双引号 C="$A+10" echo $C # 单引号 D=‘$A+10’ echo $D E=`ls` echo $E F=$(ls) echo ${F} # 同$F
-
位置参数
位置参数是一种在调用shell程序的命令中按照各自的位置决定的变量,是在shell脚本之后输入的参数。位置参数之间使用空格进行分割,shell取第一个位置参数的值赋给shell脚本中的$1,第二个位置参数的值赋给$2,以此类推。$0是一个特殊变量,其内容为当前shell脚本的文件名,所以$0不是一个位置参数。
#! /bin/bash # $# 是一个预定义变量,表示位置参数的数量 echo The para count is: $# echo The bash script name is: $0 echo The first pos para is: $1 echo The second pos para is: $2
-
预定义变量
预定义变量是在shell脚本一开始就定义的变量,shell脚本预定义了若干个变量供用户使用,所有的预定义变量都由$和另一个字符构成。常见的shell预定义变量如下所是:
预定义变量 含义 $# 位置参数数量 $* 所有位置参数的内容 $? 命令执行后的返回状态 0:正常 1:错误 $$ 当前进程的进程号 $! 后台运行的最后一个进程号 $0 当前执行的进程名 (shell程序的文件名) $@ 传递给脚本或者函数的所有参数内容 示例代码
#! /bin/bash echo "Current file name: $0" echo "First Para is : $1" echo "Second Para is : $2" echo "Inputed Para1: $@" echo "Inputed Para2: $*" echo "Total Parameter number: $#" echo "Current process id is: $$" exit 1
Note
$* 和 $@ 都表示传递给函数或者脚本的所有参数,不被双引号包含时,都以"$1","$2","$3"...的形式输出所有参数。当被双引号包围时,"$*"会将所有的参数作为一个整体输出,以"$1 $2 $3 ... $n"的形式进行输出。"$@"会将各个参数分开,以"$1" "$2" "$3" ... "$n"的形式输出所有参数。示例代码
#! /bin/bash # 无双引号 echo "\$*="$* # 有双引号 echo "\"\$*\"=""$*" # 无双引号 echo "\$@="$@ # 有双引号 echo "\"\$@\"=""$@" # 循环输出 echo "Print each param in \$@" for var in $@ do echo var is: $var done echo "Print each param in \"\$@\"" for var in "$@" do echo var is: $var done echo "Print each param in \$*" for var in $* do echo var is: $var done echo "Print each param in \"\$*\"" for var in "$*" do echo var is: $var done
条件判断
测试表达式
在shell的各种条件结构和流程控制结构中都要进行各种测试,然后根据测试结果的不同执行不同的分支,通常会与if等条件语句相结合,来完成测试判断,减少程序执行错误。
几种条件测试语法
- test 测试表达式
- [ 测试表达式 ]
- [ [ 测试表达式 ] ]
- ((测试表达式))
条件测试语法比较
test | [ ] | [ [ ] ] | (()) | |
---|---|---|---|---|
边界是否需要空格 | 需要 | 需要 | 需要 | 不需要 |
逻辑操作符 | ! -a -o | ! -a -o | ! && || | ! && || |
整数比较操作 | -eq -gt -lt -ge -le | -eq -gt -lt -ge -le | -eq -gt -lt -ge -le 或 = > >= < <= |
-eq -gt -lt -ge -le 或 = > >= < <= |
字符串比较操作 | = == != | = == != | = == != | 不支持 |
文件操作 | -d -f -e -s -r -x -w L -nt -ot |
-d -f -e -s -r -x -w L -nt -ot |
-d -f -e -s -r -x -w L -nt -ot |
不支持 |
是否支持通配符匹配 | 不支持 | 不支持 | 不支持 | 不支持 |
文件测试操作符
文件测试操作符 | 说明 |
---|---|
-d | 文件存在且类型为目录,返回真 |
-f | 文件存在且类型为文件,返回真 |
-e | 文件存在返回真 |
-s | 文件存在且大小不为0,返回真 |
-r | 文件存在且可读,返回真 |
-w | 文件存在且可写返回真 |
-x | 文件存在且可执行,返回真 |
-L | 文件存在且为链接文件,返回真 |
f1 -nt f2 | 文件f1比文件f2新,返回真 |
f1 -ot f2 | 文件f1比文件f2旧,返回真 |
test 示例代码
#! /bin/bash
echo "Test the file myfile.txt.\n"
if test -e ./myfile.txt ; then # 文件存在
echo "File \"myfile.txt\" exist.\n"
if test -d ./myfile.txt ; then # 是否为目录
echo "File \"myfile.txt\" is a directory.\n"
else
echo "File \"myfile.txt\" is file.\n"
if test -r ./myfile.txt -a -w ./myfile.txt ; then # 可读 且 可写
echo "File \"myfile.txt\" can read and write.\n"
elif test -x ./myfile.txt ; then # 可执行
echo "File \"myfile.txt\" can execute.\n"
else
echo "File \"myfile.txt\" can not both read and write and execute.\n"
fi
fi
else
echo "File \"myfile.txt\" not exist.\n"
fi
输出:
[ ]示例代码
#! /bin/bash
echo "Test the file myfile.txt.\n"
if [ -e ./myfile.txt ] ; then # 文件存在
echo "File \"myfile.txt\" exist.\n"
if [ -d ./myfile.txt ] ; then # 是否为目录
echo "File \"myfile.txt\" is a directory.\n"
else
echo "File \"myfile.txt\" is file.\n"
if [ -r ./myfile.txt ] && [ -w ./myfile.txt ] ; then # 可读 且 可写
echo "File \"myfile.txt\" can read and write.\n"
elif [ -x ./myfile.txt ] ; then # 可执行
echo "File \"myfile.txt\" can execute.\n"
else
echo "File \"myfile.txt\" can not both read and write and execute.\n"
fi
fi
else
echo "File \"myfile.txt\" not exist.\n"
fi
字符串测试操作符
常用字符串测试操作符 | 说明 |
---|---|
-n | 若字符串长度不为0,返回真 |
-z | 若字符串为空,返回真 |
str1 == str2 | 字符串相等 |
str1 != str2 | 字符串不相等 |
note : == 和 !=两端都要有空格
#! /bin/bash
str1="hello"
str2="world"
# test 判断
if test -z $str1 -o -z $str2 ; then
echo "str1 or st2 is empty.\n"
else
if test $str1 == $str2 ; then
echo "str1 equals to str2.\n"
else
echo "str1 not equals to str2.\n"
fi
fi
# [] 判断
if [ -z $str1 ] || [ -z $str2 ] ; then
echo "str1 or st2 is empty.\n"
else
if [ $str1 == $str2 ] ; then
echo "str1 equals to str2.\n"
else
echo "str1 not equals to str2.\n"
fi
fi
比较运算符 (整数比较)
表示方法1 | 表示方法2 |
---|---|
-gt | > |
-ge | >= |
-lt | < |
-le | <= |
-eq | == |
#! /bin/bash
var1=3
var2=4
if test 3 >= 4 ; then
echo "var1 equals to var2.\n"
else
echo "var1 not equals to var2"
fi
逻辑运算
表示方法1 | 表示方法2 | 说明 |
---|---|---|
-a | && | 与 |
-o | || | 或 |
! | ! | 非 |
case条件判断语句
case条件语句相当于if多分支语句,但是其格式更加的工整规范,适合实现系统服务启动脚本应用
```bash
#! /bin/bash
read -n1 -p "Are you sure to start the routine(y or n) ?" choice
echo # 换行
case $choice in
[yY]) # y or Y
echo "Starting the routine." # 会自动换行
;;
"n" | "N") # n or N
echo "Exiting the routine."
;;
*) # 相当于default
echo "Unexpected input."
esac
read -n1 -p "Please input content(char): " content
echo
case $content in
[a-z]) # 范围a-z
echo "Lower case letter."
;;
[A-Z])
echo "Uper case letter."
;;
[0-9])
echo "number."
;;
*)
echo "Unknown content"
esac
```
循环
数组
在shell脚本中,采用括号来定义数组,数组元素使用空格隔开
#! /bin/bash
# 数组定义
array=(1 2 3 4 5)
array1[0]=1
array1[1]=1
array1[2]=1
array1[3]=1
# 读取数组元素
ele=${array[0]}
echo -e "First element in array is $ele" # 添加 -e可实现换行 \c表示不换行
# 获取数组长度
len=${#array[@]} # 等效于 ${#array[*]}
# 获取特定元素的长度
eleLen=${#array[0]}
echo Array length is: $len
echo Element len is: $eleLen
# 数组遍历 (不带下标)
for var in ${array[@]} ; do
echo Value: $var
done
array2=("Hello" "world" "test" "thus")
# 数组遍历 (带下标)
for((i=0; i<${#array2[@]};i++)); do
echo Value is: ${array2[i]}
if test $i -eq 1 ; then
echo "Element length is ${#array2[i]}"
fi
done
for语法格式
for 条件
do
命令
done
# 或者
for 条件 ; do
命令
done
while循环
while 条件
do
命令
done
循环控制语句
命令 | 说明 |
---|---|
break n | n表示跳出循环的层数,如果省略,则表示跳出整个循环 |
continue n | n表示退到第n层循环继续,如果省略,则表示跳过本次循环 |
exit n | 推出当前shell程序,n为上一次执行的返回值,在下一个shell里面可以通过$?接收n的指 |
示例代码
#! /bin/bash
# 单层break
while true ; do
read -n1 -p "Please input a number: " number
echo
case $number in
[0-9])
echo You input a number: $number
;;
*)
echo It is not a number.
break # 也可修改为continue
;; # note 可写可不写
esac
done
# 多层break
for var1 in 1 2 3 ; do
for var2 in 7 8 9 ; do
if [ $var1 -eq 3 -a $var2 -eq 9 ] ; then
break 2 # 跳出两层循环
else
echo -e "Result: $var1 + $var2 \n"
fi
done
done
函数
与其他编程语言一样,shell脚本也支持函数,函数必须线定义再使用
函数的定义格式如下所示:
func_name () {
command1
command2
...
[return value]
}
函数的返回值为return语句返回的指,如果没有return语句,则会将最后一条命令执行的结果作为返回值。且shell函数的返回值只能是整数,一般用来表示执行成功与否,0表示成功,其他值表示失败。如果return了其他的数据类型,比如字符串,则会出现错误提示:"numeric argument required".
如果需要返回字符串,可以在函数外预先定义一个字符串变量,在函数中用来接收函数的执行结果,在函数执行完成之后,再去访问预先定义的变量,获取函数的执行结果
#! /bin/bash
# define function
hello () {
echo -e "Hellow everyone."
}
# invoke function
hello
带有返回值的函数
#! /bin/bash
# 定义函数
funcSum () {
read -p "Please input one number: " num1
read -p "Please input another number: " num2
return $(($num1 + $num2))
}
# 调用函数
funcSum
# 获取函数调用返回值
ret=$?
echo Sum is: $ret
函数传参
在shell脚本调用函数的时候,可以向其传递参数,在函数内部可通过$n的指来获取参数的值,注意当n>=10的时候,需要通过${n}来获取参数的值.
#! /bin/bash
funcPara () {
# 参数
echo "Para 0 is: $0" # 脚本文件的文件名
echo "Para 1 is: $1"
echo "Para 2 is: $2"
echo "Para 3 is: $3"
# 获取所有参数
echo "All para are: $*"
}
funcPara 1 2 3 4
note
unset除了可以删除变量,也可以删除函数,删除函数的时候需要加上.f选项(unset具体使用见后续Linux命令):
$unset .f funcName
此外,如果需要直接从终端调用函数,可以将函数定义在主目录下的.profile文件中,每次登陆后,在命令提示父后面可直接调用函数
输入输出重定向
Unix命令默认从标准输入(stdin)设备获取输入,将结果输出到标准输(stdout)出设备。
输出重定向
命令的输出不仅可以是标准输出,还可以将其转移向文件,这被称为输出重定向,输出重定向的语法为
command > filename
输出重定向会覆盖文件原有的内容,如果希望文件内容不被覆盖,可按照如下方法将输出内容追加到文件末尾
command >> filename
command >> filename
...
输入重定向
类似于输出重定向,Unix命令也可以从文件获取输入,语法为
command < file
重定向之后,本来需要从键盘获取输入的命令会转移到文件读取内容,例如统计某文件的行数:
wc -l < filename
补充内容
一般情况下,每个Linux命令运行,都会打开以下三个文件:
- 标准输入文件(stdin) stdin文件的文件描述符为0,Linux命令默认从stdin读取数据
- 标准输出文件(stdout) stdout文件的文件描述符为1,Linux命令默认向stdout输出数据
- 标准错误文件(stderr) stderr文件的文件描述符为2,Linux命令默认向stderr写入错误信息
如果希望将stderr重定义到文件,可采用如下的方法:
command 2>file
command 2>>file # 追加
如果希望将标准输出和标准错误输出合并到同一个文件,可采用如下的方法
# 文件描述符1和文件描述符2合并,重定向到文件输出
command > file 2>&1
command >> file 2>&1
# 读如也可以采取合并
# n <& m 将输入文件m和n进行合并
/dev/null文件
在执行某个命令的时候,如果希望不再屏幕上显示输出结果,可以将输出结果重定向到/dev/null,这是一个特殊的文件,写入这个文件的内容都会被丢弃,如果从该文件读取内容,将读取不到任何内容。可以将输出重定向到这个文件,起到禁止命令执行结果输出的效果。
command>/dev/null 2>&1
xargs参数过滤
xargs (extended arguments)是给命令传递参数的一个过滤器,也是组合多个命令的一个工具,