Shell语法

一、shell的概论

1.1 概念

shell是用C语言编写的程序,是用户通过命令行和操作系统沟通的语言,shell既是一种命令语言,也是一种程序设计语言.简述来说,shell脚本可以直接在命令行执行,即shell在终端的命令行中逐行执行.shell脚本也可以将一套的逻辑放到文件中,直接执行该文件,方便复用.

1.2 类型

在Linux中常见的shell脚本有很多种,常见的有

  • Bourme Shell(user/bin/sh或者/bin/sh)
  • Bourme Again Shell(/bin/bash)
  • C Shell(/bin/bash/csh)
  • K Shell(/bin/bash/ksh)
  • ...

shell编程只要有写代码的文本编辑器和一个能解释执行的脚本解释器就可以了,shell脚本在Linux可以直接执行,在windows终端中需要gitGit Bash Here才能执行.Windows CMD常用命令大全(值得收藏)
Linux系统一般默认使用bash,所以以下的都是bash中的语法.
shell文件开头的开头都要加上!# /bin/bash,表明bash为脚本的解释器(即编译器)

1.3 脚本示例

新建一个test.sh文件,内容如下

#! /bin/bash
echo "Hello world!"

1.4 运行方式

作为可执行文件

acs@1463acb54038:~$ ls
homework
acs@1463acb54038:~$ vim test.sh
acs@1463acb54038:~$ ls
homework  test.sh
acs@1463acb54038:~$ chmod +x test.sh
acs@1463acb54038:~$ ls
homework  test.sh
acs@1463acb54038:~$ bash test.sh
Hello World!

二、变量

2.1 Shell变量的种类

运行shell脚本时,会同时存在三种变量:

  • 局部变量:局部变量在脚本或命令中定义,仅在当前的shell实例中有效,其他shell启动的程序不能访问局部变量
  • 环境变量:所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常的运行.
  • shell变量:shell变量是由shell程序设置的特殊变量,shell变量有一部分是环境变量,有一部分是局部变量.
  • 将局部变量变为环境变量:
    acs@1463acb54038:~$ name=lwy
    acs@1463acb54038:~$ export name
    acs@1463acb54038:~$ declare -x name
    acs@1463acb54038:~$ bash
    acs@1463acb54038:~$ echo ${name}
    lwy

2.2 变量的使用

# 变量的定义
name='lwy' # 单引号定义字符串
name="lwy" # 双引号定义字符串
name=lwy # 不加引号也表示字符串
# 使用变量 在定义过之前加上$,最好是加上{},用来识别变量的边界
echo ${name} # 输出lwy
echo $name # 输出lwy
echo ${name}hhh # 输出lwyhhh
# 只读变量和删除变量
readonly boy=lwy # 定义只读变量
boy=zyc # 会报错,只读变量不能修改
unset name # 删除name变量
echo name # 会输出为空

2.3 字符串

# 字符串有三种方式 单引号 双引号 不加引号
name='lwy'
name="lwy"
name=lwy
# 字符串拼接
echo "hello, "${name}"!"
echo 'hello,'${name}'!'
echo 'hello,${name}!'
# 分别输出 hello,lwy/hello,lwy/hello,${name}!
# 获取字符串长度 #获取长度
str="qwer"
echo ${#str}
# 提取子字符串
echo ${str:0:2}
# 输出qw

三、传递参数

3.1 文件参数变量

当我们在执行Shell脚本时,向脚本传递参数,脚本内获得参数的格式为:$n,n表示第n个参数,$0表示执行的文件名(含文件路径)

#! /bin/bash
# 该文件的文件名为test.sh
str="传递参数"
echo "这个文件名为"${0}
echo "who's boy?"${1}
echo "who's girl?"${2}

可以将该脚本设置执行权限,并执行脚本

chmod +x test.sh
./test lwy zyc
:<<abc
这是文件的注释,输出的结果如下
这个文件名为./test.sh
who's boy?l wy
who's girl?zyc
abc

3.2 其他参数相关变量

参数 说明
$# 获取参数的个数
$@ 输出" ${1}, ${2}, ${3} "的值(数组)
$* 输出" ${1}, ${2}, ${3} "的值(字符串)
$? 上一条命令的退出状态,即exitcode,0表示正常退出,其他表示错误
$(command) 返回common这条命令的stdout(可嵌套)
\command\ 返回这个command这条命令的stdout(可嵌套)
$$ 当前脚本的进程id

四、数组

4.1 定义和读取数组

Shell数组中可以放多个不同类型的值,Bash Shell只支持一维数组,初始化时不需要定义大小,数组元素下标 由0开始,Shell数组由括号来表示,元素由==空格==分隔开,语法格式如下
array_name=(value1 value2 value3 ... valuen)

ararry = (a 123 "lwy" 'c')
#可以用下标来定义数组
array[0]=a
array[1]=123

可以直接下标读取

echo ${array[0]} # 输出a
echo ${array[1]} # 输出123

4.2 关联数组

Bash支持关联数组,可以使用任意的字符串、或整数作为下标来访问数组元素
关联数组使用declare命令来声明

declare -A arrayname
declare -A site = (["name"]="lwy" ["age"]=18 ["sex"]=man)
echo ${site["name"]}

使用@和*可以数组的所有元素

echo "数组的元素为"${site[@]}
echo "数组的元素为"${site[*]}
# 数组前加!表示获取数组所有的键值
echo "数组的元素为"${!site[*]}
# 获取数组的长度
echo "数组的长度为"${#site[*]}
echo "数组的长度为"${#site[@]}

Bash支持关联数组(也就是哈希表),可以使用任意的字符串、或者整数作为下标来访问数组元素
关联数组的命令用declare命令来声明
declare -A arrayname

declare -A student
student["boy"]=lwy
student["girl"]=zyc
student["one"]=hhhh
echo ${!student[*]}
echo ${student[*]}

五、expr命令

expr 用于求表达式的值,格式为
expr 表达式

表达式说明:

  • 用空格隔开每一项
  • 用反斜杠转义\
  • 包含空格和其他特殊字符的字符串要用引用括起来
  • expr 会在stdout输出结果.如果为逻辑关系表达式,则结果为真时,stdout输出1,否则输出0
  • expr也会有exit code:,如果为逻辑关系表达式,则结果为真时,exit code为0,否则为1

5.1 基本的运算符

假设变量a为10,变量b为20

  • 运算符号:+ - * / % ()
运算符 说明 举例
+ 加法 \expr $a + $b\结果为30
- 减法 \expr $b - $a\结果为10
* 乘法 \expr $a \ * $b\结果为200
/ 除法 \expr $b / $a\结果为2
% 取余 \expr $b % $a\结果为0
() 改变优先级 \expr \ ($b + $a\ ) \ * a \结果为300

5.2 逻辑关系表达式

  • 逻辑符号: | & < <= = == ! >= >
  • 假设变量a为10,变量b为20
运算符 说明 举例
| \expr $a \ \| $b\结果为10
& \expr $b \ & $a\结果为20
> 大于 \expr $a \ $b\结果为0
== 等于 \expr $b \ = \ = $a\结果为0
! \expr !a\结果为0

六、read命令

read命令用于从标准输入中读取单行数据。当读到文件结束符时,exit code为1,否则为0。
参数说明:

  • -p :后面可以接提示信息
  • -t:后面可以跟秒数,定义输入字符等待时间
read name
lwy
echo ${name} # 输出为lwy
read -p "what's your name?" -t 10 name
lwy
what's your name? lwy
echo ${name}
lwy

七、echo命令

7.1显示普通字符

echo string
#例子
echo "It is a test" # 字符串
echo It is a test # 可以省略""
echo "\"It is a test\"" # 转义字符  输出“It is a test”

7.2显示开启转义

echo -e "Hello\n"
echo "lwy"
:<<abc
输出
hello

lwy
abc
echo -e "hello \c"
echo lwy
#输出 hello lwy

7.3 显示结果定向至文件

echo "Hello World"  out.txt # 将内容以覆盖的方式out.txt中
cat out.txt # 显示 Hello World

7.4显示命令执行的结果

echo `date`
# 输出的结果为:Thu Jun 8 22:33:17 CST 2023

八、printf命令

printf命令用于格式化输出,类似于C/C++中的printf函数.
默认不会再字符串的末尾添加换行符

echo "Hello, lwy"
# 输出 Hello lwy
printf "Hello, lwy\n"
# 与上面的运行结果一致

命令格式
printf format-string [ ]

  • format-string :为格式控制字符串
  • arguments: 参数列表
#! /bin/bash
printf "%s %s %s\n" lwy love zyc
printf "%10d\n" 1234
printf "%10.2f\n" 123.245616
printf "who're you? %s\n" lwy

九、Test命令与判断符号[]

test 与 expr 的不同 :

  • test表示的是exitcode值,exitcode为0是表示真,1则表示假
  • expr表示的是stdout值,stdout为1表示真,0表示假
  • test和expr作用:test更多用来检测,expr更多的是求表达值

9.1 逻辑运算符 && 和 ||

  • && 表示,||表示
  • expr1 && expr2:当expr1为假时,直接忽略expr2
  • expr1 || expr2:当expr1为真时,直接忽略expr2

9.2 test命令

可以在命令行中输入help test,可以查看test命令的用法
test命令用于判断文件类型,以及对变量做比较

9.3 变量比较

9.3.1 整数比较

命令格式:

test &a -gt $b # a是否大于b

例如:

test 1 -lt 2
echo $?
test 1 -lt 2 && echo "true" || echo "false"
# exitcode的值为0,所以输出为0
# 1 < 2 输出为true
命令 表示意义
-eq 等于
-ne 不等于
-gt 大于
-ge 大于等于
-le 小于等于
-lt 小于

9.3.2 字符串比较

命令格式

test -z string
命令 表示意义
test -z string 判断string是否为空,如果为空,则返回0
test -n string 判断string是否为非空,如果为非空,则返回0
test string1 == string2 判断两个字符串是否相等
test string != string2 判断两个字符串是否不相等
test string \< string2 字符串也可以做比较

9.4 文件格式的判断

9.4.1 文件类型的判断

命令格式:

test -e filename # 判断文件是否存在
命令 表示意义
-e 判断文件是否存在
-f 判断是否为文件
-d 判断是否为文件夹

9.4.2 文件权限的判断

命令格式

test -x filename # 判断文件是否可执行
命令 表示意义
-x 判断文件是否为可执行文件
-r 判断文件是否为可读文件
-w 判断文件是否为可写文件
-s 判断文件是否为空文件

9.4.3 多重条件的判断

命令格式:

test -r filename -a -x filename #判断是否同时成立
test -r filename -a -x filename && echo "true" || echo "false"
命令 表示意义
-a 判断两个条件是否同时成立
-o 两条件是否至少一个成立
! 取反 test ! -x file 当文件不可执行是返回0(也就是true)

十、判断语句

判断语句为if..then 或 case ..esac类似于C/C++的if-else和switch
condition:表示的是exitcode的值

10.1 if ..then

  • 单层if
    命令格式:

    if condition
    then
    语句1
    语句2
    fi
  • 单层if-else
    命令格式:

    if condition then
    语句1
    语句2
    esle
    语句3
    语句4
    fi
  • 多层if-elif-elif-else
    命令格式:

    if condition then
    语句1
    语句2
    elif condition then
    语句3
    语句4
    esle
    语句5
    语句6
    fi

10.2 case ..esac

命令格式:

# 类似于C/C++的default
case $变量名称 in
值1)
    语句1
    语句2
    ;;
 值2)
    语句3
    语句4
    ;;
*)
    语句5
    语句6
    ;;
esac

示例:
a = 4

case $a in
1)
    echo "${a}" 等于1
    ;;
2)
    echo "${a}" 等于2
    ;;
3)
    echo "${a}" 等于3
    ;;
*)
    echo 其他
    ;;
esac
# 输出其他

十一、循环语句

循环语句包括

  • for ..in .. do .. done
  • for .. in $(seq 1 10) do .. done {1 .. 10} 或者 {a .. z}
  • for ((...;...;...)) do .. done
  • while ..do .. done 循环
  • until.. do ..done 循环
  • break命令
  • continue命令

11.1 for循环

  • for ..in .. do .. done
    示例 :
# 输出a abc 123
for i in a abc 123
do
    echo $i
done
# 输出当前文件夹的文件名
for i in `ls`
do
    echo $i
done
  • for .. in $(seq 1 10) do .. done
    示例 :
for i in $(seq 1 10)
do
    printf "%d " $i
done
输出 1 2 3 4 5 6 7 8 9 10
# {1..10}不能有空格
# 输出1 2 3 4 5 6 7 8 9 10
for i in {1..10}
do
    echo $i
done
  • for((..;..;..)) do .. done # 类似于C/C++中的for循环, 不需要在意空格
    示例 :
# 输出的结果是 0 1 2 3 4 5 6 7 8 9
for ((i = 0; i < 10; i ++))
do
    echo $i
done

11.2 while和until

while与until的作用是一样的,但是判断条件不同,while是判断为真则运行语句,until是判断为假则运行语句.
命令格式:

# while的格式
while condition
do
    语句1
    语句2
done
until的格式
until condition
do
    语句1
    语句2
done

示例:

while [ $i -ne 0].
do
    echo $i
    let "i -= 1"
    echo $i
    ((i --))
done

11.3 continue和break

countinue和break两个语句与C/C++基本上类似,不过要注意的点就是break不能跳出case语句.
示例::

# continue:
# 输出1 3 5 7 9
for ((i = 0; i < 10; i ++))
do
    if [ `expr $i % 2` -eq 0 ]
    then
        continue
     else
         echo $i
         fi
done

# break:
# 输出0 1 2 3 4 5
for i in {1..10}
do
    case $i in
        5)
            break;
            ;;
        *)
            echo $i
            ;;
    esac
done

11.4 死循环处理的方式

ctrl + c即可
或者可以直接关闭进程

  • 使用top命令查看进程的PID
  • 使用kill -9 PID即关闭此进程

十二、函数

shell中的函数类似于C/C++中的函数,但return的返回值与C/C++不同,返回的是exit code,取值为0 - 255,0表示正常结束
如果想获取函数的输出结果,可以通过echo输出到stdout中,然后通过$(function_name)来获取stdout的结果.
函数的return值可以通过$?来获取.

函数的命令格式:

[function] function_name() { # 前缀的function可以省略,参数可以直接与 $1 $2去获取
    语句1
    语句2
    ...
}

示例:

# 输出 res = 0 std = lwy is a boy
func(){
    local name="lwy" # 局部变量
    echo $name is a $1
    return 0 #可以不写,默认返回值为0
}
res=$?
stdout=$(func boy)
echo "res = $res"
echo "stdout = $stdout"

用函数实现斐波拉契数列 f(n) = f(n - 1) + f(n - 2)

# 实现斐波拉契数列 f(n) = f(n - 1) + f(n - 2)
#! /bin/bash
func(){
    if [ $1 -eq 1 ]-
    then
        echo 0
        return 0
    fi  
    if [ $1 -eq 2 ]-
    then
        echo 1
        return 0
     fi
     a=$(func $(expr $1 - 1))
     b=$(func $(expr $1 - 2))
     echo $(expr $a + $b) }

echo $(func 10)

十二、exit命令

exit命令是用来退出当前shell进程的,并返回一个退出状态,使用$?可以接受这个退出状态.
exitreturn的区别:exit和return都返回的是exitcode值,但exit是退出的是整个shell进程,而return只是退出整个函数.
示例:

# 不会输出 没有杀死整个进程.
while read name
do
    echo $name
    if [ $name == "lwy"]
    then
        echo "exit"
        exit 2
    fi
done
echo "没有杀死整个进程"

十三、文件重定向

每个进程默认打开三个文件描述符

  • stdin:标准输入,从命令行读取数据,文件描述符为0
  • stdout:标准输出,从命令行输出数据,文件描述符为1
  • stderr:标准错误输出,向命令行输出数据,文件描述符为2

命令表

命令 说明
command file stdout以覆盖的方式重定向到file
command < file stdin以覆盖的方式重定向到file
command > file stdout以追加的方式重定向到file
command n file 以文件描述符n并追加的方式重定向到file
command n> file 以文件描述符n并覆盖的方式重定向到file

输入输出重定向:

echo "lwy hhh zyc"  output.txt
read str < output.txt
# 输出lwy hhh zyc
echo $str

同时重定向stdin和stdout

#! /bin/bash
read a
read b
echo $(expr ${a} + ${b})
3
4
# 创建test.sh脚本
# 再创建input.txt
./test.sh < input.txt  output.txt
# 查看内容为7
cat output.txt

十四、引入外部脚本

类似于C/C++的include操作,bash可以引用其他脚本的代码
命令格式

# .和 filename之间有一个空格
. filename
# 或者
source filename

示例:
创建脚本 test1.sh

#! /bin/bash
boy=lwy
girl=zyc

然后创建脚本test2.sh

#! /bin/bash
# 输出 lwy is a boy zyc is a girl
echo "${lwy} is a boy"
echo "${zyc} is a girl"

总结

以上就是shell所有的基本内容,其中还有许多没有涉及,如let的使用 top type等一些命令,大家去网上搜一搜就行啦,以上的内容大部分是根据www.acwing.com的yxc老师来写的,所以大家如果能看到最后的话也可以去多多关注yxc老师.