linux shell 编程超详细总结
shell 脚本,也就是将想要执行的命令按顺序保存到一个文本文件中,根据需要给予一定的执行流程控制,然后给出解释器路径和可执行权限,这就是一个完整的 shell脚本了。
一. shell 基础
- 查看支持的
shell
[root@mini2 ~]# cat /etc/shells
/bin/sh (Bourne shell)
/bin/bash (Bourne-Again shell)
/sbin/nologin
/usr/bin/sh
/usr/bin/bash
/usr/sbin/nologin
/bin/zsh
/bin/dash
/bin/tcsh
/bin/csh
[root@mini2 ~]#
- 查看当前
shell
env | grep -i shell
echo $SHELL
- 查看当前的
shell
层级
echo $SHLVL
shell
查找命令的顺序
1、以路径方式(首先绝对路径,然后相对路径)开始
2、查找别名
3、shell 内置命令
4、环境变量的路径echo $PATH
type –a command
可以查看执行命令的顺序type command
查看命令类型echo
命令echo
输出的如果有特殊字符,就需要使用转义字符-n
不换行,echo 默认是要换行的-e
表示使用 -e 选项才能使转义符生效\c
不换行\n
换行\t
跳格TAB- 占位符
:
:
该命令什么都不做,但执行后会返回一个正确的退出代码,即exit 0
。比如在 if 语句中,then 后面不想做任何操作,但是又不能空着,这时就可以使用“:”
来解决,相当于Python
的pass
i=1
if [ "$i" -ne 1 ];then
:
else
echo "$i is equal 1"
fi
二. 执行方式
2.1 使用当前 shell 执行
不需要执行权限,在该脚本中的变量会保存在当前 shell 中,用echo $variablename
可以查看,与source
和.
的效果相同。
source /root/test.sh
. /root/test.sh
2.2 使用子 shell 执行
下面两种本质上是一样的,都是调用解释器
- 直接用全路径
/path/test.sh
便可执行(需要执行权限chmod +x /path/test.sh
) - 用解释器
bash test.sh
或者sh test.sh
(不需要执行权限)
注意 :在执行脚本时加上-x
参数,这样会显示每一个被执行到的命令,并在显示的前面添加'+'
号然后空格。也可将其加入到脚本中:set -x
打开跟踪,set +x
取消跟踪。
三. 变量
3.1 变量分类
shell 的变量分为:普通变量、本地变量、环境变量、位置变量、特殊变量
普通变量
在不同的 shell 里面拥有自己的 shell 变量,叫做普通变量,在另外一个 shell 环境下无法使用这些变量。比如a=100
本地变量
生效范围为当前 shell 进程中某代码片断,通常指函数,在函数体中使用 local
声明的变量,就是本地变量local NAME=VALUE
,当函数调用结束时,本地变量会自动被销毁。
环境变量
为了让这些变量在不同的 shell 中使用,可以使用 export
将普通变量设置成环境变量。环境变量的范围包括当前 shell 和其子 shell。只有当当前 shell 进程及其 shell 子进程全部结束退出时,环境变量才会被销毁。
- 第一种定义方法:
name=mage; export name
- 第二种定义方法:
export name=mage
- 第三种定义方法:
declare -x name=mage
位置变量
$1, $2, ...
来表示,用于让脚本在脚本代码中调用通过命令行、函数传递给它的参数。最多可以出来9个参数,$10
以后的需要使用()
$(10)
。
特殊变量
$#
传送给shell程序的位置参数的数量$?
最后命令完成码或在shell程序内部执行的shell程序(返回值,执行正确时是0)$0
shell程序的名称$*
将传入的参数作为单个字符串,这个字符串以空格分隔(实际上是$IFS
的值分隔)$@
将各个参数分为独立多个位置参数传入$$
本程序的PID$!
上一个命令的PID
$@ 和 $*的区别
当 $*
和 $@
不被双引号" "包围时:它们之间没有任何区别,都是将接收到的每个参数看做一份数据,彼此之间以空格来分隔。
当 $*
和 $@
被双引号" "包围时:"$*"
会将所有的参数从整体上看做一份数据,而不是把每个参数都看做一份数据,"$@"
仍然将每个参数都看作一份数据,彼此之间是独立的。
- x.sh
#!/bin/bash
echo -e "\na:"
for arg in $*; do echo "$arg"; done
echo -e "\nb:"
for arg in $@; do echo "$arg"; done
echo -e "\nc:"
for arg in "$*"; do echo "$arg"; done
echo -e "\nd:"
for arg in "$@"; do echo "$arg"; done
# 执行结果
a:
1
2
3
4
5
b:
1
2
3
4
5
c:
1 2 3 4 5
d:
1
2
3
4
5
3.2 变量声明
- 定义方法一
变量不需要提前定义,直接赋值即可,定义变时是等号两边没有空格。variablename=value
,清除变量unset variablename
。清除变量时注意没有$
。 - 定义方法二
declare
命令用于控制变量相关属性,declare attribute variable
,attribute
表示变量属性,variable
表示名称,属性取值有:-p
: 打印变量类型、值-i
: 定义为整数-a
: 定义为数组变量-A
: 定义为字典变量-l
: 该变量会将大写字母转换成小写字母-u
: 该变量会将小写字母转换成大写字母-n
: 定义一个指向其他变量的引用,一旦当其他变量改变,那么它也跟着改变(字符串、数组、字典都允许)-r
: 定义为只读变量-f
: 显示所有自定义函数,包括名称和函数体-x
: 变量设置成环境变量,随后的脚本和程序可以使用,即和export
等效
declare -a arr1=(2 3 4)
echo ${arr1[@]}
2 3 4
declare -n arr2=arr1
echo ${arr2[@]}
2 3 4
arr1[1]=99
echo ${arr2[@]}
2 99 4
read
标准输入读取变量
从键盘读入多个变量,当数据个数不够时,从左到右对应赋值,没有的为空。如果数据多余,最后一个变量被赋予剩余的所有数据。-p
选项表示有一个提醒语句。read -p "input your name :" NAME
-t
选项表示输入时间限制,超过之后就不能输入了
read a b
for i in `seq $a $b`
do
echo `expr $i \* 10`
done
四. 符号区分
注释符# 美元符$ 单引号 双引号 倒引号 $() \
- 1 注释符
#
除了#!/bin/bash
里的#特殊,其他都表示注释 - 2 美元符
$
变量符。与反斜杠转义符相反,使其后的普通字符作为变量名,如$a
表示变量a
的值。变量字符长度超过1个时,用{}
括起来 - 3 单引号
'
被引起的字符全部做普通字符,即全部原样 - 4 双引号
"
引号内的内容,除$
转义符\
倒引号这三个保留特殊功能,其他字符均做普通字符。 - 5 倒引号
`
倒引号即数字1键旁边的那个键。引号内的字符串当做shell命令行解释执行,但只能执行一行命令,得到的结果取代整个倒引号括起来的部分。 - 6 执行命令
$()
可以嵌套执行命令(多个命令以不同的顺序),从里到外执行,比如$(rpm -qc $(rpm -qf $(which useradd)))
可以查看一下已安装软件的配置文件 - 7 反斜线
\
反斜线是转义字符,它能把特殊字符变成普通字符。在某个字符前面利用反斜杠(\)
能够阻止 shell 把后面的字符解释为特殊字符。
五. 算术运算
算术元素使用$((expression)
或者expr
命令来对表达式求值,expr
比较局限,通常情况下还是使用$((expression)
求值。可用的操包括基本的+ - * / % **
,另外还有一些比较复杂的记法+= -= *= /=
,以及自增variable++
和自减variable--
等,运算符的前后可以有空格。
# 相乘运算
expr 4 \* 6
24
# 控制运算顺序
i=0
echo $((i = (i + 10) * 10))
100
# 另外一种写法
i=0
i=$(((i + 10) * 10))
echo $i
100
# 2的3次方
echo $((2 ** 3))
8
# 表达式真伪判断:
# 如果表达式为真,就为result赋值1(真)
# 如果表达式为假,则为result赋值0(假)
i=9
result=$(( i >= 0 && i <= 100 ))
echo $result
1
六. 条件测试
test
和[ ]
是等价的,在[ ]
中注意表达式与中括号间有空格,比较结果为真返回0,为假返回1。
# 方式一:判断参数个数
if [ $# -eq 0 ]
then
:
elif [ $# -eq 1 ]
then
:
else
:
fi
# 方式二:判断参数个数
if [ $# -eq 0 ]; then
:
fi
# 方式三:判断参数个数
[ $# -eq 0 ] && { echo "..."; exit; }
- 直接在命令行前加 if 判断执行结果
if who | grep "root" >/dev/null
then
echo "root is logged on"
fi
6.1 数字比较
# 数字比较
test 1 –lt 4
echo $?
# 数字比较
[ 1 –lt 4 ]
echo $?
# 判断边上是否为数字
var=a99
[ "$var" -eq "$var" ] 2>/dev/null
if [ $? -ne 0 ]; then
echo $var is not number
fi
数字的比较符号-lt
小于-le
小于等于-gt
大于-ge
大于等于-eq
等于-ne
不等于
6.2 文件判断
[ -d "/path/mydoc" ]
echo $?
常用判断符号-f
存在且是普通文件-e
测试目录或文件是否存在-d
存在且是目录-s
存在且字节数大于0-r
存在且可读-w
存在且可写-x
存在且可执行
6.3 字串判断
# 判断字符串非空
[ -n "a" ]
# 判断字符串为空
[ -z "" ]
# 字符串相等和不相等
[ "ab" == "ab" ]
[ "ab" != "ab" ]
6.4 逻辑测试
- 逻辑与或非
-a
逻辑与 或者&&
-o
逻辑或 或者||
!
逻辑非 - 多个条件同时满足(与)
count=5
if [ "$count" -ge 0 -a "$count" -lt 10 -a "$count" -eq 5 ]; then
echo "good"
fi
- 多个条件或只要满足一个(或)
count=20
if [ "$count" -le 0 -o "$count" -le 10 -o "$count" -eq 20 ]; then
echo "good"
fi
- 与和或同时使用是有时需要更改优先级,此时则使用
\( \)
括起来,注意需要空格。
a=1
b=2
c=10
if [ \( "$a" -eq 0 -o "$b" -eq 2 \) -a "$c" -eq 10 ];then
echo good
fi
七. 循环结构
7.1 case结构
每个分支条件后必须以两个分号结尾。
case "$#" in
0) echo "输入了0 个参数";;
1) echo "输入了1 个参数";;
*) echo "输入了多个参数";;
esac
7.2 for结构
for
循环
for i in `seq 1 9`; do
echo $( expr $i \* 10 )
done
- 将
for
循环抛到后台执行wait
命令表示等待所有的后台程序返回在执行后面的操作。
func1(){
echo start $1
sleep 5
echo stop $1
}
for i in {1..8}; do
func1 $i &
done
wait
7.3 while结构
i=1
sum=0
while [ $i -le 100 ]; do
sum=$(($i + $sum))
let i++
done
echo $sum
八. 重定向
- 代表编号
0 标准输入(STDIN)
1 标准输出(STDOUT)
2 标准错误(STDERR) - 操作符号
> 或者 >> 表示正确结果输出覆盖、追加(1省略了,写上也可以)
2> 或者 2>> 表示错误结果输出覆盖、追加
&> 或者 &>> 表示正确和错误的都输出覆盖、追加
- 三种等价的重定向
ls 1>/dev/null 2>/dev/null
ls &>/dev/null
ls >/dev/null 2>&1
- 不同写法但相同效果的重定向区别
# stdout 和 stderr 都直接送到 file 中,
# 会出现两个同抢占 file 的管道,file会被打开两次,
# stdout 和 stderr 输出的信息会互相覆盖。
command >file 2>file
# 将 stdout 直接送向 file,stderr 进入 stdout,
# 再被送往 file,此时 file 只被打开了一次,
# 也只使用了一个管道 FD1,它包括了 stdout 和 stderr 的内容。
command >file 2>&1
# 从 IO 效率上,前一条命令的效率要比后面一条的命令效率要低,
# 所以在编写 shell 脚本的时候,常用command > file 2>&1 这样的写法。
九. 函数
9.1 定义函数
- 如下四中方式定义函数,
{ }
是匿名函数。
func_name(){
...函数体...
}
function func_name{
...函数体...
}
function func_name(){
...函数体...
}
{ ...函数体... }
- 获取当前执行的函数名称
func1(){
echo ${FUNCNAME[0]}
}
func1
9.2 函数调用
- 在脚本内调用执行
在脚本内调用直接写函数名即可。注意:函数的定义必须写在调用的前面。 - 载入到当前shell
使用set
命令可以查看当前 shell 的所有函数、变量等
source /path/custom.sh
. /path/custom.sh
9.3 函数传参
函数传参和脚本传参类似,$1
表示第一个参数,$2
表示第二个参数。
func1(){
echo $@
}
func1 a b c d
bash ./x.sh
a b c d
9.4 函数返回值
- 函数返回值为数字
函数返回值及函数的执行状态码,如果不显示指出,那么有函数内的最后一条指令的状态码确定,0
表示成功,非0
表示失败。
func1(){
echo $@
return 100
}
func1 a b c d
echo $?
- 函数返回为字符串
func1(){
echo $@
}
return_string=$(func1 a b c d)
echo $return_string
9.5 匿名函数
匿名函数最后一个语句必须有;
结尾,并且函数的大括号内部必须留有空格。
[ -f /etc/notexists ] || { echo 999; echo 888; exit 9; }
十. 字符串
10.1 字符串分片
即取字符串的指定位置的字符,:
前后数组分别表示:从什么位置开始、取多少个元素,位置从 0 开始。
$ string=01234567890abcdefgh
$ echo ${string: 7}
7890abcdefgh
$ echo ${string: 7:0}
$ echo ${string: 7:2}
78
$ echo ${string: 7:-2}
7890abcdef
$ echo ${string: -7}
bcdefgh
$ echo ${string: -7:0}
$ echo ${string: -7:2}
bc
$ echo ${string: -7:-2}
bcdef
10.2 获取脚本参数1
取指定位置参数的指定字符:直接获取传入脚本的位置参数里面的字符串。
set --
表示把输入参数清空,然后把后面字符当做脚本的输入参数。使用${}
来获取指定位置的参数:echo ${1:7:2}
表示取第一个参数的第7下标的字符,连续取 2 位。
# $1 等于 01234567890abcdefgh
# $2 等于 where
set -- 01234567890abcdefgh where
$ echo ${1:7}
7890abcdefgh
$ echo ${1:7:0}
$ echo ${1:7:2}
78
$ echo ${1:7:-2}
7890abcdef
$ echo ${1: -7}
bcdefgh
$ echo ${1: -7:0}
$ echo ${1: -7:2}
bc
$ echo ${1: -7:-2}
bcdef
10.3 获取脚本参数2
取指定范围的参数:获取脚本传入的位置参数。
$ set -- 1 2 3 4 5 6 7 8 9 0 a b c d e f g h
$ echo ${@:7}
7 8 9 0 a b c d e f g h
$ echo ${@:7:0}
$ echo ${@:7:2}
7 8
# 取的个数必须大于0
$ echo ${@:7:-2}
bash: -2: substring expression < 0
$ echo ${@: -7:2}
b c
# 如果是在脚本里面执行 -bash 就是实际的脚本名称
# -bash 表示当前shell
$ echo ${@:0}
-bash 1 2 3 4 5 6 7 8 9 0 a b c d e f g h
$ echo ${@:0:2}
-bash 1
$ echo ${@: -7:0}
10.4 字符串split
# 方法一
while read name shell; do
echo $name $shell
done < <(awk -F':' '{print $1,$NF}' /etc/passwd)
# 方法二:
awk -F':' '{print $1,$NF}' /etc/passwd | {
while read name shell;do
echo $name $shell
done
}
# 方法三:
while read -r line;do
readarray -d: -t arr <<< "$line"
echo ${arr[0]} ${arr[6]}
done < /etc/passwd
# 方法四:
echo $my_str | tr ":" " "