已复制
全屏展示
复制代码

linux shell 编程超详细总结


· 14 min read

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 后面不想做任何操作,但是又不能空着,这时就可以使用“:”来解决,相当于Pythonpass
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 ":" " "
🔗

文章推荐