shell

1.shell概述

Shell是命令解释器(command interpreter),是Unix操作系统的用户接口,程序从用户接口得到输入信息,shell将用户程序及其输入翻译成操作系统内核(kernel)能够识别的指令,并且操作系统内核执行完将返回的输出通过shell再呈现给用户,下图所示用户、shell和操作系统的关系:

image-20210411153245838

一个系统可以存在多个shell,可以通过cat /etc/shells命令查看系统中安装的shell。

[root@centos ~]# cat /etc/shells
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash
/bin/tcsh
/bin/csh

操作系统内核(kernel)与shell是独立的套件,而且都可被替换;不同的操作系统使用不同的shell; 同一个kernel之上可以使用不同的shell。 也可以查看当前shell环境是哪种:

root@centos ~]# echo $SHELL
/bin/bash

shell脚本:可执行的Linux命令或语句不在命令行状态下执行,而是通过一个文件执行时,我们将这个文件称为shell脚本(可执行命令的组合)。在此脚本中,我们可以使用一些编程语法来进行一些任务操作。 如:变量、类型、分支结构、循环结构、数组、函数等语法。 在shell脚本里,必须指定一种shell命令解释器,它通过解释器解释运行。shell脚本文件的后缀为.sh

2.第一个shell脚本

  • 编写脚本打印"hello shell"
#!/bin/bash
echo "hello shell"
  • 脚本执行
[root@centos ~]# sh hello.sh
  • 说明
1.脚本的第一行要写解析器的位置  声明用什么解释器来解释这个shell脚本
2.#!/bin/bash
3.要执行这个脚本,有两种方式
第一种:解析器的位置 脚本文件名
如:bash 文件名.sh

第二种运行方法:
修改文件的权限,添加执行权限
./文件名.sh

# 练习:写个shell脚本,显示你自己的名字

# 练习:新建两个文件夹,aa和bb,bb里有1.txt,aa里有2.txt,写个shell脚本,把aa,bb里的文件互换
  • 注释
注释的文字不是代码的一部分,解析器看到你是注释文字,那么就不会进行解析
1.单行注释用#,#后面的所有文字都是注释,shell首行的#不是注释
2.多行注释:

:<<!
#echo 1111  #xxxx
2222
3333
!
编写一个脚本 
提示用户输入 名字 性别 家庭地址
然后打印名字 性别 和 家庭地址(每行打印一个)

3.变量

3.1 什么是变量

  • 变量是用来临时保存数据的,该数据是可以变化的数据。
  • 如果某个内容需要多次使用,并且在代码中重复出现,那么可以用变量代表该内容。这样在修改内容的时候,仅仅需要修改变量的值。

3.2 变量的定义

3.2.1 变量的定义

  • 变量名=变量值

    变量名:用来临时保存数据的

    变量值:就是临时的可变化的数据

变量名=变量值
[root@centos ~]# A=hello

3.2.2 变量的定义规则

1.变量名区分大小写
2.变量名不能有特殊符号
3.变量名不能以数字开头
4.不能使用bash中的关键字
5.变量名尽量做到见名知意
6.等号两边不能有任何空格

3.2.3 变量的定义方式

1.直接赋值给一个变量
[root@centos ~]# A=hello
2.命令执行结果赋值给变量
[root@centos ~]# B=`date +%F`
3.交互式定义变量 read
让用户自己给变量赋值
语法: read [选项] 变量名

# 只读变量
使用readonly来修饰变量,使变量成为只读变量,只读变量是不能修改的
[root@centos ~]# readonly B=shell

# 删除变量
使用unset可以删除一个变量
[root@centos ~]# unset A
  • read 常见选项:
选项 释义
-p 定义提示用户的信息
-n 定义字符数(限制变量值的长度)
-s 不显示(不显示用户输入的内容)
-t 定义超时时间,默认单位为秒(限制用户输入变量值的超时时间)

3.3 变量的使用

  • 使用一个定义过的变量,只要在变量名前面加上$,就可以获取变量里的值
[root@centos ~]# A=hello
[root@centos ~]# echo $A

3.4 变量的分类

3.4.1 本地变量

  • 当前用户自定义的变量,当前进程中有效,其他进程及当前进程的子进程无效

3.4.2 环境变量

  • 当前进程有效,并且能够被子进程调用
    • env查看当前用户的环境变量
    • 可以使用 ”export 变量名=变量值“ 设置一个环境变量

3.4.3 全局变量

  • 全局所有的用户和程序都能调用

  • 解读相关配置文件

文件名 说明 备注
~/.bashrc 当前用户的bash信息,用户登录时读取 局部。定义别名、umask、函数等
~/.bash_profile 当前用户的环境变量信息,用户登录时读取 局部。
~/.bash_logout 当前用户退出当前shell时最后读取 局部。定义用户退出时执行的程序等
~/.bash_history 当前用户的历史命令 局部。history -w保存历史记录 history -c清空历史记录
/etc/bashrc 全局的bash信息 全局。所有用户都生效
/etc/profile 全局环境变量信息 全局。系统和所有用户都生效

说明:以上文件修改后,都需要重新 source 让其生效或者退出重新登录。

3.4.4 系统变量

  • shell本身已经固定好了它的名字和作用.
内置变量 含义
$? 上一条命令执行后返回的状态;状态值为0表示执行正常,非0表示执行异常或错误
$0 当前执行的程序或脚本名
$# 脚本后面接的参数的个数
$* 脚本后面所有参数,参数当成一个整体输出,每一个变量参数之间以空格隔开
$@ 脚本后面所有参数,参数是独立的,也是全部输出
$1~$9 脚本后面的位置参数,$1表示第1个位置参数,依次类推
${10}~$ 扩展位置参数,第10个位置变量必须用{}大括号括起来(2位数字以上扩起来)
$$ 当前所在进程的进程号,如echo $$

4.字符串操作

4.1 字符串概述

字符串:用引号包含的若干个字符组成的整体叫字符串
如:"12345","约吗?","1","2","hello world"
  • bash中的引号
双引号"":弱引用,会把引号的内容当成整体来看待,允许通过$符号引用其他变量值
单引号'':强引用,会把引号的内容当成整体来看待,禁止引用其他变量值,shell中特殊符号都被视为普通字符
反撇号``:命令替换,反撇号和$()一样,引号或括号里的命令会优先执行,如果存在嵌套,反撇号不能用

4.2 字符串操作

1.字符串的拼接
把若干个字符串组合在一起的操作,叫字符串的拼接
#!/bin/bash
name="zhangsan"
p1="hello $name"
echo $p1 #打印出来显示hello zhangsan
p2="hello ${name}abc"
p3=$p1$p2     
echo $p3    #hello zhangsanhello zhangsan
p4=$p1 $p2  #错误
p4="$p1 $p2"
echo $p4

2.获取字符串的长度(统计一下字符串中有多少个字符)
x="hello" --该字符串的长度为5
格式:${#变量名}
获取x变量中存储的字符串的长度
echo ${#x} #5
注意:字符串中有符号或空格也算一个字符
如:
y="hello world!"
echo ${#y}  #12

3.截取字符串
格式:${变量名:开始截取的字符下标:截取的长度}
如:
x="zhangsan"
echo ${x:2:3}
注意:下标是从0开始数

4.3 字符串数组

1.数组:相同数据类型的数据集合
2.字符串数组:若干个字符串组成的集合叫字符串数组
3.shell中数组是没有大小限定 
4.定义数组的格式:
数组名=(值1 值2 值3...)
5.数组里元素的位置是从0开始数,通常也叫数组的下标,可以针对数组里的位置单独放数据
如:
myarr=("aa" "bb" "cc")
myarr[0]="kk"
6.读取数组里的元素
	1.格式:${数组名[下标]}
	如要要取出上面数组中的bb元素,${myarr[1]}
	2.把数组中的所有元素一次性取出,并打印
	echo ${数组名[@]}
	如要取出上面数组中所有的元素并打印
	echo ${myarr[@]}
	3.获取数组的长度(获取数组里有多少个元素)
	${#myarr[@]}
	4.获取数组中单个元素的长度
	${#myarr[0]}
	
练习:写在脚本里:定义一个数组,把你所在省份的周边省放入到这个数组,然后在一个一个打印出来,每打印一个,还要在下面一行打印出该元素长度,最后一次性取出所有的元素,再打印数组的长度

5.运算符

5.1 算数运算符

  • 算数运算:默认情况下,shell就只能支持简单的整数运算

  • 运算内容:加(+)、减(-)、乘(*)、除(/)、求余数(%)

  • shell 进行数值运算的符号

表达式 举例 说明
$(( )) echo $((1+1)) 可以进行复杂运算, * 号不需转义
$[ ] echo $[10-5] 可以进行复杂运算, * 号不需转义
expr expr 10 / 5 expr程序中,数值与运算符之间需要空格隔开,乘(*)运算符需要用转义符( \ )转义,expr不能做幂运算
let n=1;let n+=1 等价于 let n=n+1 let n*=2 等价于let n=n*2,不能使用let n**=2

5.2 关系运算符

判断参数 含义
-eq 相等
-ne 不等
-gt 大于
-lt 小于
-ge 大于等于
-le 小于等于

5.3 逻辑运算符

判断符号 含义 举例
-a 和 && 逻辑与 [ 1 -eq 1 -a 1 -ne 0 ]
[ 1 -eq 1 ] && [ 1 -ne 0 ]
-o 和 || 逻辑或 [ 1 -eq 1 -o 1 -ne 1 ]
[ 1 -eq 1 ] || [1 -ne 1]

&& 前面的表达式为真,才会执行后面的代码

|| 前面的表达式为假,才会执行后面的代码

; 只用于分割命令或表达式

5.4 字符串运算符

判断参数 含义
-z 判断是否为空字符串,字符串长度为0则成立
-n 判断是否为非空字符串,字符串长度不为0则成立
string1 = string2 判断字符串是否相等
string1 != string2 判断字符串是否相不等

5.5 文件测试运算符

  • test命令的作用:用于检测某个条件是否成立,它可以对数值,字符,和文件三个方面进行检测

  • 判断文件类型

判断参数 含义
-e 判断文件是否存在(任何类型文件)
-f 判断文件是否存在并且是一个普通文件
-d 判断文件是否存在并且是一个目录
-L 判断文件是否存在并且是一个软连接文件
-s 判断文件是否存在并且是一个非空文件(有内容)
  • 判断文件权限
判断参数 含义
-r 当前用户对其是否可读
-w 当前用户对其是否可写
-x 当前用户对其是否可执行
  • 判断文件新旧
判断参数 含义
file1 -nt file2 比较file1是否比file2新
file1 -ot file2 比较file1是否比file2旧
file1 -ef file2 比较是否为同一个文件,或者用于判断硬连接,是否指向同一个inode

6.流程控制语句

6.1 if 结构

流程判断1

if [ 条件 ];then
        命令
fi

if [ 条件 ]
then
	命令
fi


if test 条件;then
    命令
fi


if [[ 条件 ]];then
    命令
fi

[ 条件 ] && command

6.2 if...else 结构

流程判断2

if [ 条件 ];then
        命令1
else
        命令2
fi


[ 条件 ] && 命令1 || 命令2

6.3 if...elif...else 结构

流程判断3

if [ 条件1 ];then
        命令1      
    elif [ 条件2 ];then
        命令2      
    else
        命令3
fi


如果条件1满足,执行命令1后结束;如果条件1不满足,再看条件2,如果条件2满足执行命令2后结束;如果条件1和条件2都不满足执行命令3结束.

6.4 嵌套结构

流程判断4

if [ 条件1 ];then
        命令1        
        if [ 条件2 ];then
            命令2
        fi
 else
        if [ 条件3 ];then
            命令3
        elif [ 条件4 ];then
            命令4
        else
            命令5
        fi
fi


如果条件1满足,执行命令1;如果条件2也满足执行命令2,如果不满足就只执行命令1结束;
如果条件1不满足,不看条件2;直接看条件3,如果条件3满足执行命令3;如果不满足则看条件4,如果条件4满足执行命令4;否则执行命令5

7.循环语句

7.1 for 循环语句

  • 基本语法结构
for 变量 in {列表}
do
	命令 
done

# 或者

for 变量 in 值1 值2 值3
do
    命令
done
     
# 或者

for (( expr1;expr2;expr3 ))
do
	命令
done

循环打印1-5
1
2
3
4
5

i=i+1    i=i-1    i=i*1
i+=1     i-=1     i*=1
i++

for (( i=1;i<=5;i++ ))
do
	echo $i
done

sum=0
for (( i=1;i<=100;i++ ))
do
	sum=$[$sum+$i]
done
echo $sum

# expr1:定义变量并赋初值
# expr2:决定是否进行循环(条件)
# expr3:决定循环变量如何改变(决定循环什么时候退出)

7.2 while 循环语句

  • 条件成立就进入循环,条件不成立则退出循环

  • 基本语法

while 表达式
do
	命令
done


for (( i=1;i<=100;i++ ))
do
	命令
done

i=1
while (( i<=100 ))
do
	命令
	i=$[$i+1]
done
用while循环求1-100的累加和


while :
while (( 永真表达式 ))

while  [ 1 -eq 1 ] 或者 (( 1 > 2 ))
do
	命令
done

7.3 until 循环语句

  • 条件为假就进入循环,条件为真就退出循环

  • 语法结构

until 表达式
do
	命令
done

直到满足表达式之前我都去进行循环

7.4 跳出循环 breakcontinue

  • break:打断;马上停止执行本次循环,执行循环体后面的代码
  • continue:继续;表示循环体内下面的代码不执行,重新开始下一次循环
a=10
while (( 1 ))
do
	echo "一直停不下来 $a"
	let "a++"
	if [ $a == 16 ]
	then
		break
	fi
done



n=0
while (( $n<100 ))
do
	let "n++"
	if [ $n == 66 -o $n == 88 -o $n == 99 ]
	then
		continue
	fi
	echo $n
done

7.5 嵌套循环

  • 一个循环体内又包含另一个完整的循环结构,称为循环的嵌套。
  • 每次外部循环都会触发内部循环,直至内部循环完成,才接着执行下一次的外部循环。
  • for循环、while循环和until循环可以相互嵌套。
1
12
123
1234
12345


外部循环:打印换行,并且换5行 ,循环5次

内部循环:打印12345数字

for (( i=1;i<=5;i++ ))
do
    for (( j=1;j<=$i;j++ ))
    do
    echo -n $j
    done
echo
done

8. case 语句和函数

8.1 case 语句

  • case 语句为多重匹配语句,匹配成功则执行相应命令
  • 语法结构
说明:pattern表示需要匹配的模式

case var in             
	值1)                 
    command1            
    ;;                  
	值2)
    command2
    ;;
	值3)
    command3
    ;;
    *)                  # default,不满足以上模式,默认执行*)下面的语句
    command4
    ;;
esac                    # esac表示case语句结束

8.2 函数

  • shell中允许将一组命令集合语句形成一段可用代码,这些代码块称为shell函数
  • 给这段代码起个名字称为函数名,后续可以直接调用该段代码的功能
  • 函数声明
函数名()
{
  函数体(一堆命令的集合,来实现某个功能)   
}
或者
function 函数名()
{
   函数体(一堆命令的集合,来实现某个功能)
}
  • 函数调用
函数名 [参数]
  • 函数的传参和返回值
1.函数传参
a.在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,
例如,$1表示第一个参数,$2表示第二个参数...
但是,当n>=10时,需要使用${n}来获取参数值
如:
#!/bin/bash
add(){
    
    c=`expr $1 + $2 `
    echo "c=$c"
    echo "${10}"
}
add 10 20 
b.传递参数到函数内,在调用函数时,在函数名的旁边写参数
add 100 200 300 400 500 600 700 800 900 1000

练习:
1.写一个减法功能的函数,参数是从函数外部传递
#!/bin/bash
sub(){
    a=`expr $1 - $2`
    echo "结果:$a"
}
sub 20 10

2.写一个有加减乘除功能的函数,运算的数据和运算符都从外部传递到函数内
#!/bin/bash
n=0
count(){
    case $3 in
    	"+")
    		n=`expr $1 + $2`
    		;;
        "-")
            n=`expr $1 - $2`
            ;;
        "*")
            n=`expr $1 \* $2`
            ;;
        "/")
            n=`expr $1 / $2`
            ;;
    esac
    echo "结果:$n"
    
}

count 20 10 "*"


2.函数的返回值
#!/bin/bash
add(){
    c=`expr $1 + $2 `
    echo "c=$c"
    return $c
}
add 10 20 
echo "$?"

注意:
1,shell中通过return返回是有限制的,只能返回整型,最大返回值是255
2,有多个函数返回时,在最后使用$?获取返回值时返回的是最后一个函数的返回值
#!/bin/bash
add(){
    c=`expr $1 + $2 `
    echo "c=$c"
    return $c
}
add 10 20 

add2(){
    c=`expr $1 + $2 `
    echo "c=$c"
    return $c
}
add2 100 200

echo "$?"