Shell 脚本 基础 学习 笔记 (超详细,适合新手观看学习)

Shell脚本语言

第一行的#!/bin/bash标识该Shell脚本是由哪个shell解释:

shell 复制代码
#!/bin/bash

Shell关键字

  • echo:打印内容到屏幕上
  • exec:执行另一个Shell脚本
  • read:读标准输入
  • expr:对整数型变量进行算术运算
  • test:用于测试变量是否相等、是否为空、文件类型等

Shell_demo01.sh

shell 复制代码
#!/bin/bash
echo="Hello Shell"

read ABC
echo "ABC is $ABC"

expr $ABC - 4

test "Hello"="Hello Shell"
test -d /home/kanglei/code

exec /home/kanglei/code/shell_demo02.sh

exit

Shell_demo02.sh

sh 复制代码
#!/bin/bash
echo "I'm Kavien"

初次执行会报错没有权限

shell_demo01.sh: line 12: /home/xiaoming/code/shell_demo02.sh: Permission denied

shell_demo01.sh: line 12: exec: /home/xiaoming/code/shell_demo02.sh: cannot execute: Permission denied

然后我们将两个文件的权限都赋值为777

sh 复制代码
chmod 777 shell_demo01.sh 
chmod 777 shell_demo02.sh 

再次执行即可执行成功,结果如下

sh 复制代码
99
ABC is 99
95
I'm Kavien

Shell变量

命名规则

  • 定义变量时,变量名不加美元符号$
  • 变量名和等号之间不要出现空格,但是可以使用下划线 - '_'
  • 一般变量名用大写
  • 命名只能是英文字母、数字和下划线,首个字符不能以数字开头
  • 不能使用标点符号和关键字

使用变量

  • 使用一个已经被定义过的变量。只要在变量名前面加美元符号并将变量加上花括号即可。
sh 复制代码
my_name="xiaoming"
echo ${my_name}

赋值变量

  • 赋值处必须是一个整体,不能有空格
  • 想要有空格,请使用单引号或者双引号将赋值处进行包围

自定义变量

  • 只读变量
    • 使用readonly将变量定义为只读变量,只读变量值不可改变
  • 删除变量
    • 使用unset可以删除变量,变量被删除后不能再次使用,unset不能删除只读变量
  • 全局变量
    • 使用export将变量设置为全局变量
sh 复制代码
#!/bin/bash
echo "I'm Kavien"

# 定义普通变量
NAME=xiaoming
# 定义只读变量
readonly COUNTRY=SHANGHAI
# 定义全局变量
export HOME="shanghai pudong"

# 打印变量的值
echo $NAME
echo $COUNTRY
echo $HOME

# 删除变量
unset NAME
unset COUNTRY
unset HOME

echo $NAME 
echo $COUNTRY
echo $HOME

exit

执行结果如下

sh 复制代码
I'm Kavien
xiaoming
SHANGHAI
shanghai pudong
shell_demo02.sh: line 18: unset: COUNTRY: cannot unset: readonly variable

SHANGHAI

命令行交互read

有时候我们希望在脚本运行时能根据用户的输入决定脚本后续执行逻辑,比如在安装插件的时候经常会让用户选择输入[N/Y]的时候。

shell 复制代码
read -p "Please input [N/Y]: " yn
if ["$yn" == "N" -o "$yn" == "n" ]; then
	echo "NO"
elif ["$yn" == "Y" -o "$yn" == "y" ]; then	
	echo "YES"
fi

read命令的使用形式为

shell 复制代码
read [-pt] variable
	参数p:后面可以接提示符
	参数t:后面可以接秒数
eg:
read -p "please input your name" -t 5 name
# 表示将输入内容赋值给变量name,用户有5秒钟的输入时间。

定义变量类型declare

declare命令的使用形式如下:

shell 复制代码
declare [-aixr] variable
	参数a:将variable定义为数组
	参数i:将variable定义为整形(integer)
	参数x:将variable设置成环境变量,类似于export的作用
	参数r:variable为readonly类型,值不能被更改

默认情况下,变量的赋值内容都是字符类型的。例如sum=100+300+500,我们希望echo $sum输出的是一个求和表达式100+300+500。如果想要输出求和后的值,可以使用declare命令declare -i sum=100+300+500,结果输出echo $sum的是900。

预定义变量

预定义变量常用来获取命令行的输入

  1. $0:脚本文件名
  2. $1-9:第1-9个命令行参数名
  3. $#:命令行参数个数
  4. $@:所有命令行参数
  5. $*:所有命令行参数
  6. $?:前一个命令的退出状态
  7. :执行的进程ID

环境变量默认就存在,常用的有以下几种:

  • HOME:用户主目录
  • PATH:系统环境变量PATH
  • TERM:当前终端
  • UID:当前用户ID
  • PWD:当前工作目录,绝对路径

Shell字符串

  • 字符串是用单引号或者双引号或者可以不使用引号的整体。

  • 单引号字符串的限制

    • 单引号字符串中的变量值是无效的
    • 单引号中不能使用转义字符
  • 双引号字符串的优点

    • 双引号字符串中可以变量
    • 双引号中可以出现转义字符
  • 拼接字符串

    sh 复制代码
    result1="hello,${COUNTRY}!"
    result2="hello,"${COUNTRY}"!"
    
    result3='hello,'${COUNTRY}'!'
    result4='hello,${COUNTRY}!'
    echo $result1 $result2
    echo $result3 $result4

    运行结果为:

    sh 复制代码
    hello,SHANGHAI! hello,SHANGHAI!
    hello,SHANGHAI! hello,${COUNTRY}!
  • 获取字符串的长度

    sh 复制代码
    echo ${#COUNTRY}等价于${#COUNTRY[0]}
  • 提取子字符串

    sh 复制代码
    # 从字符串中第二个字符开始截取3个字符
    echo ${COUNTRY:1:3}
    # 从倒数第二位开始,截取三个字符,最后一个字符下标为-1
    echo ${COUNTRY:(-2):3}
    # 从第二位开始截取到最末尾,第一个字符下标为0
    echo ${COUNTRY:2}

    【注】第一个字符的索引值为0。

  • 查找子字符串(哪个字母先出现就计算哪个)

    shell 复制代码
    string="runoob is a great site"
    echo `expr index "$string" io` # 输出4

    【注】以上脚本中的`是反引号,而不是单引号'。

  • 删除字符串

    #从前往后截取,%从后往前截取

    如果使用两个#或者两个%,则表示将符合的最长数据删除

    shell 复制代码
    str="https://www.baidu.com/123/abc"
    # 删除前面的https://
    echo ${str#https://} # www.baidu.com/123/abc
    # 删除后面的/abc
    echo ${str%/abc} # https://www.baidu.com/123
  • 替换字符串

    shell 复制代码
    # 将https替换成HTTP
    echo ${str/https/HTTP}
    • 使用一个斜杠(/)表示只替换第一个遇到的字符
    • 使用两个斜杠(//)则表示替换全部负荷的字符
    • 使用#匹配以指定字符开头的字符串(#删除字符串的前缀)
    • 使用%匹配以指定字符结尾的字符串(%删除字符串的后缀)
    shell 复制代码
    echo ${str/b/B} # https://www.Baidu.com/123/abc
    echo ${str//b/B} # https://www.Baidu.com/123/aBc
    echo ${str/#abc/ABC} # https://www.baidu.com/123/abc
    echo ${str/%abc/ABC} # https://www.baidu.com/123/ABC
    echo ${str/#ht/HT} # HTtps://www.baidu.com/123/abc
    echo ${str/%ht/HT} # https://www.baidu.com/123/abc
  • 字符串默认值

    shell 复制代码
    # 如果变量name没有赋过值,则给一个默认值default,否则使用指定的值
    echo $name # 输出为空
    echo ${name-default} # defalut
    
    name="abc"
    echo ${name-default} # abc
    # 如果将变量设置为"",则会认为赋过值
    name=""
    echo ${name-default} # 输出为空
    # 如果变量内容为""或者变量未初始化则给默认值,可以在-前面加个冒号,使用:-
    name=""
    echo ${name-default} # 输出为空
    echo ${name:-default} # default
  • 字符串split成数组

    shell 复制代码
    # 以空格为分隔符分隔字符串成数组
    s="a b c d e"
    a=($s)
    echo ${a[*]} # a b c d e
    echo ${a[3]} # d
    # 需要指定特定字符进行分隔且原字符中又没有空格时,将特定字符替换成空格,然后进行分隔
    s="a,b,c,d,e"
    a=(${s//,/ }) # 两个斜线 // 表示全局替换的意思。逗号,是要被替换的模式,空格``是替换后的文字。
    # 如果字符串中本身带有空格,并且希望分隔符不为空格
     s="a b,c d e,f,g,h,i,j"
     old_ifs="$IFS"
     echo s #s
     echo $s # a b,c d e,f,g,h,i,j
     echo $old_ifs # 
     IFS=","
     echo $s # a b c d e f g h i j
     a=($s)
     echo $a # a b
     echo ${a[@]}# a b c d e f g h i j
     echo ${a[2]} # f
     echo ${a[1]} # c d e
     echo ${a[3]} # g
     echo ${a[4]} # h
     echo ${#a[0]} # 3
     echo ${#s} # 19
     echo ${#a[*]} # 7 
  • 字符串的包含

    shell 复制代码
    # 有时需要判断字符串str1中是否包含字符串str2,使用=~操作符
    str1="hello"
    str2="ell"
    if [[$str1 =~ $str2 ]];then
    	echo "$str1 contain $str2"
    fi
  • ``符号

    这个符号在数字键1的左侧,与单引号很类似,但其功能与单引号都有不同,该符号中的命令会被执行

    shell 复制代码
    d=`date`
    echo $d

    如果不想用这个符号,可以用$() 替换

    shell 复制代码
    e=$(date)
    echo $e

Shell中的集合类型

数组(array)

数组定义和赋值

数组中的元素用括号包围,各元素之间用空格隔开。

shell 复制代码
array_name=(v0 v1 v2 v3 v4)
# 重新设置指定元素的内容
array4_name[2]=v22
echo ${array_name[2]} # v22

数组元素访问

shell 复制代码
echo ${array_name[*]} # v0 v1 v2 v3 v4
echo ${array_name[@]} # v0 v1 v2 v3 v4

数组元素下标从0开始,想要访问指定位置的元素,使用[]指定下标值

shell 复制代码
echo ${array_name[0]} # v0
echo ${array_name[3]} # v3

获取数组长度

shell 复制代码
# 获取整个数组的长度
echo ${#array_name[@]} # 5
echo ${#array_name[*]} # 5
# 获取数组中单个元素的长度
echo ${#array_name[2]} # 2

map

map类型中存储的是键值对,map变量使用如下

shell 复制代码
# 定义map变量
declare -A map=(["a"]="1" ["b"]="2")
# 输出所有的key
echo ${!map[@]}
# 输出所有的value
echo ${map[@]}
# 输出指定key对应的value
echo ${map["a"]}
echo ${map["c"]}
# 添加元素
map["c"]="3"
echo ${map["c"]}
# map中键值对的个数
echo ${#map[@]}

Shell运算

【注意】

  1. 表达式和运算符之间要有空格,例如2+2是不正确的,必须写成2 + 2
  2. 完整的表达式要被``两个反引号包含。

算数运算符

下面列出了常用的算数运算符,假定变量a为10,b为20:

运算符 说明 举例
+ 加法 expr $a + $b 结果为 30
- 减法 expr $a - $b 结果为 -10
* 乘法 expr $a \* $b 结果为 200
/ 除法 expr $b / $a 结果为 2
% 取余 expr $b % $a 结果为 0
= 赋值 a=$b 把变量 b 的值赋给 a
== 相等。用于比较两个数字,相同则返回true。 [ $a == $b ] 返回 false
!= 不相等。用于比较两个数字,相同则返回true。 [ $a != $b ] 返回 true

【注意】

  • 条件表达式要放在方括号之间,并且要有空格,例如[$a==$b]是错误的,必须写成[ $a == $b ]
  • 乘号(*)前边必须加反斜杠(\)才能实现乘法运算

关系运算符

关系运算符只支持数组,不支持字符串,除非字符串的值是数字。

运算符 说明 举例
-eq 检测两个数是否相等,相等返回 true。 [ $a -eq $b ] 返回 false。
-ne 检测两个数是否不相等,不相等返回 true。 [ $a -ne $b ] 返回 true。
-gt 检测左边的数是否大于右边的,如果是,则返回 true。 [ $a -gt $b ] 返回 false。
-lt 检测左边的数是否小于右边的,如果是,则返回 true。 [ $a -lt $b ] 返回 true。
-ge 检测左边的数是否大于等于右边的,如果是,则返回 true。 [ $a -ge $b ] 返回 false。
-le 检测左边的数是否小于等于右边的,如果是,则返回 true。 [ $a -le $b ] 返回 true。

布尔运算符

运算符 说明 举例
! 非运算,表达式为 true 则返回 false,否则返回 true。 [ ! false ] 返回 true。
-o 或运算,有一个表达式为 true 则返回 true。 [ $a -lt 20 -o $b -gt 100 ] 返回 true。
-a 与运算,两个表达式都为 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ] 返回 false。

逻辑运算符

运算符 说明 举例
&& 逻辑的 AND [[ $a -lt 100 && $b -gt 100 ]] 返回 false
|| 逻辑的 OR [[ $a -lt 100 || $b -gt 100 ]] 返回 true

字符串运算符

假定变量a为"abc",变量b为"efg":

运算符 说明 举例
= 检测两个字符串是否相等,相等返回 true。 [ $a = $b ] 返回 false。
!= 检测两个字符串是否不相等,不相等返回 true。 [ $a != $b ] 返回 true。
-z 检测字符串长度是否为0,为0返回 true。 [ -z $a ] 返回 false。
-n 检测字符串长度是否不为 0,不为 0 返回 true。 [ -n "$a" ] 返回 true。
$ 检测字符串是否不为空,不为空返回 true。 [ $a ] 返回 true。

文件测试运算符

文件测试运算用于检测Unix文件的各种属性

操作符 说明 举例
-b file 检测文件是否是块设备文件,如果是,则返回 true。 [ -b $file ] 返回 false。
-c file 检测文件是否是字符设备文件,如果是,则返回 true。 [ -c $file ] 返回 false。
-d file 检测文件是否是目录,如果是,则返回 true。 [ -d $file ] 返回 false。
-f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 [ -f $file ] 返回 true。
-g file 检测文件是否设置了 SGID 位,如果是,则返回 true。 [ -g $file ] 返回 false。
-k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 [ -k $file ] 返回 false。
-p file 检测文件是否是有名管道,如果是,则返回 true。 [ -p $file ] 返回 false。
-u file 检测文件是否设置了 SUID 位,如果是,则返回 true。 [ -u $file ] 返回 false。
-r file 检测文件是否可读,如果是,则返回 true。 [ -r $file ] 返回 true。
-w file 检测文件是否可写,如果是,则返回 true。 [ -w $file ] 返回 true。
-x file 检测文件是否可执行,如果是,则返回 true。 [ -x $file ] 返回 true。
-s file 检测文件是否为空(文件大小是否大于0),不为空返回 true。 [ -s $file ] 返回 true。
-e file 检测文件(包括目录)是否存在,如果是,则返回 true。 [ -e $file ] 返回 true。

其他检查符:

  • -S:判断某文件是否socket
  • -L:检测文件是否存在并且是一个符号链接

Shell语句

shift 命令可以用于将命令行参数向左移动一位,也就是忽略第一个参数,让 $2 作为新的 $1,以此类推。

shell 复制代码
#!/bin/bash
echo "原始参数: " $*
shift
echo "shift后参数: " $*
shift 2
echo "shift 2后参数: " $*1995

运行命令bash test.sh wo shi xiao ming ni meng hao,运行结果如下:

sh 复制代码
原始参数:  wo shi xiao ming ni meng hao
shift后参数:  shi xiao ming ni meng hao
shift 2后参数:  ming ni meng hao1995

if语句

if语句语法格式

shell 复制代码
if condition
then 
	command1
	command2
	...
	commandN
fi

可以将以上命令写为一行(适用于终端命令行提示符):

shell 复制代码
if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi

代码解释:

  • 这段代码是一个简单的条件判断语句,用于检查系统中正在运行的 SSH 进程数量是否大于 1。

  • 代码中使用了 $() 来执行命令替换。$(ps -ef | grep -c "ssh") 执行了一个命令,用于查询运行中的进程列表,然后使用 grep 过滤出包含 "ssh" 字符串的行,并使用 -c 参数计算匹配行的数量。

  • 接着,[ ... ] 是一个条件判断语句的开头;-gt 表示大于的比较运算符;1 是要比较的值。

  • 如果运行中的 SSH 进程数量大于 1,则输出 "true"。

if else语法格式

shell 复制代码
if condition
then 
	command1
	command2
else 
	command3
fi

if else-if else语法格式

shell 复制代码
if condition
then 
	command1
elif condition2
then
	command2
else
	command3
fi

if else的[...]判断语句大于使用-gt,小于使用-lt

shell 复制代码
if [ "$a" -gt "$b" ]; then
	...
fi

如果使用((...))作为判断语句,大于和小于可以直接使用><

for循环

for循环语法格式:

shell 复制代码
for var in item1 item2 ... itemN
do 
	command1
	command2
done

一行格式:

shell 复制代码
for var in item1 item2 ... itemN; do command1; command2... done;

当变量值在列表中,for循环即执行一次所有命令,使用变量名获取列表中的当前取值。命令可为任何有效的shell命令和语句。in列表可以包含替换、字符串和文件名。

in列表是可选的,如果不用它,for循环使用命令行的位置参数。

shell 复制代码
# 顺序输出当前列表中的数字
for loop in 1 2 3 4 5
do
	echo "The value is:$loop"
done
# 顺序输出字符串中的字符
for str in This is a string
do
    echo $str
done

while循环

while循环用于不断执行一系列命令,也用于从输出文件中读取数据。

shell 复制代码
while condition
do
	command
done
shell 复制代码
# 举例说明:如果int小于等于5,则输出int的值,并使int+1
int=1
while(( $int<=5))
do
	echo $int
	let "int++"
done
# 上述案例中,使用到了let命令,用于执行一个或多个表达式,变量计算中不需要加上$来表示变量

无限循环

shell 复制代码
while
do
	command
done
# 或者
while true
do
 	command
 done
# 或者
for (( ; ; ))

until循环

  • until循环执行一系列命令直至条件为true时停止
  • until循环与while循环在处理方式上刚好相反
shell 复制代码
# until语法格式
until condition
do 
	command
done

condition一般为条件表达式,如果返回值为false,则继续执行循环体内的语句,否则跳出循环。

shell 复制代码
# 使用until输出0~9的数字
a=0
until [ ! $a -lt 10 ]
do
	echo $a
	a=`expr $a + 1`
done

case...esac

case...esac为多选择语句,与其他语言中的swith...case语句类似,是一种多分支选择结构,每个case分支用右圆括号开始,用两个分号;;表示break,即执行结束,跳出整个case...esac语句,esac作为结束标记。

可以用case语句匹配一个值与一个模式,如果匹配成功,执行想匹配的命令。

shell 复制代码
case 值 in 
模式1)
	command1
	command2
	;;
模式2)
	command1
	command2
	;;
esac
  • 取值后必须为单词in,取值可以为变量或常数
  • 每个模式必须以右括号结束
  • 如果无一匹配模式,使用星号*捕获该值,再执行后面的命令。
shell 复制代码
# 脚本提示输入,寻找相匹配的项
echo '输入 1 到 2 之间的数字:'
echo '你输入的数字为:'
read aNum
case $aNum in
    1)  echo '你选择了 1'
    ;;
    2)  echo '你选择了 2'
    ;;
    *)  echo '你没有输入 1 到 2 之间的数字'
    ;;
esac
# 脚本匹配字符串
site="abc"
case "$site" in
   "abc") echo "ABC" 
   ;;
   "def") echo "DEF" 
   ;;
   "ghi") echo "GHI" 
   ;;
esac

跳出循环

break

break命令允许跳出所有循环(终止执行后面的所有循环)

shell 复制代码
# 以下脚本会进入死循环直到接收到输入数字大于5的情况,会跳出这个循环
while :
do
    echo -n "输入 1 到 5 之间的数字:"
    read aNum
    case $aNum in
        1|2|3|4|5) echo "你输入的数字为 $aNum!"
        ;;
        *) echo "你输入的数字不是 1 到 5 之间的! 游戏结束"
            break
        ;;
    esac
done

continue

continue命令不会跳出所有循环,仅仅跳出当前循环

shell 复制代码
# 当接收到数字大于5时,循环不会结束,语句echo "游戏结束"永远不会被执行
while :
do
    echo -n "输入 1 到 5 之间的数字: "
    read aNum
    case $aNum in
        1|2|3|4|5) echo "你输入的数字为 $aNum!"
        ;;
        *) echo "你输入的数字不是 1 到 5 之间的!"
            continue
            echo "游戏结束"
        ;;
    esac
done

Shell函数

定义函数

shell 复制代码
# 语法格式
function 函数名 () {
	command
}

function 函数名 {
	command
}

函数名 () {
	command
}
shell 复制代码
# 案例说明
function hello_fun(){
    echo "hello function() fun"
    echo $1 $2
    return 1
}


function hello_function{
    echo "hello function fun"
    echo $1 $2
    return 1
}

hello()
{
    echo "hello fun"
}

调用函数

shell 复制代码
# 直接使用函数名调用hello函数
hello
# 使用[函数名 函数参数]来传递参数
hello_function 1 2
# 使用[FUN=`函数名 函数参数`]来间接调用
Fun=`hello_fun 1 2`
echo $FUN

获取返回值

$?可用于获取前一个函数的返回值

shell 复制代码
hello_fun 1 2
echo $? # 返回1

定义局部变量

shell 复制代码
# 使用local来在函数中定义本地变量
fun()
{
	local x=1
	echo $x
}

Shell调试

sh -n script_name.sh检查是否有语法错误,没有任何输出则表示无语法错误。

sh -x script_name.sh执行并调试Shell脚本

shell 复制代码
# 命令
sh -x test.sh wo shi xiao ming ni meng hao
# 运行结果
+ echo 原始参数:  wo shi xiao ming ni meng hao
原始参数:  wo shi xiao ming ni meng hao
+ shift
+ echo shift后参数:  shi xiao ming ni meng hao
shift后参数:  shi xiao ming ni meng hao
+ shift 2
+ echo shift 2后参数:  ming ni meng hao1995
shift 2后参数:  ming ni meng hao1995

前缀为+表示Shell的输出,不带任何符号的表示我们程序的输出。

相关推荐
幺零九零零38 分钟前
【计算机网络】TCP协议面试常考(一)
服务器·tcp/ip·计算机网络
云飞云共享云桌面2 小时前
8位机械工程师如何共享一台图形工作站算力?
linux·服务器·网络
Peter_chq2 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
一坨阿亮3 小时前
Linux 使用中的问题
linux·运维
dsywws4 小时前
Linux学习笔记之vim入门
linux·笔记·学习
幺零九零零5 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
wclass-zhengge5 小时前
Docker篇(Docker Compose)
运维·docker·容器
李启柱5 小时前
项目开发流程规范文档
运维·软件构建·个人开发·设计规范
free6 小时前
netstat中sendq/recvq用于排查发送端发送数据的问题
服务器
小林熬夜学编程6 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法