概论

shell属于一种解释性语言,不需要编译即可运行。由于是解释性语言,运行时的效率会比较低。

例如:递归方法计算从1到n的加和。

shell有很多种,例如 shbashcshzshksh

shBourne Shell

bashBourne Again Shell

我们默认使用的是 bash,所以接下来主要讲解 bash中的语法

脚本示例

#! /bin/bash
echo "Hello, World!"

运行结果是输出一行 Hello, World!

运行方式

作为可执行文件运行:

chmod +x test.sh # 赋予脚本可以执行的权限
./test.sh # 当前路径下执行
# 以下是输出结果:
Hello, World!
#也可以有不同路径的执行命令:
/home/acs/test.sh # 绝对路径执行
~/test.sh # 家目录下执行

用解释器执行:

bash test.sh
# 以下是输出结果:
Hello, World!

注释

单行注释

#后面的即是注释部分

# 这是一行注释
echo "Hello, World!" # 这也是注释

多行注释

:<<EOF
This is the first line of notes.
This is the second line of notes.
This is the third line of notes.
EOF

其中的 EOF可以换成其他任何字符串,例如:

:<<KevinYao
This is the first line of notes.
This is the second line of notes.
This is the third line of notes.
KevinYao

:<<!
This is the first line of notes.
This is the second line of notes.
This is the third line of notes.
!

变量

定义变量

没有类型的说明,直接对变量进行赋值即可

name1=kevin # 可以不加引号
name2='kevin' # 单引号
name3="keivn" # 双引号

使用变量

name=keivn
echo $name # 输出keivn
echo ${name} # 输出kevin
echo ${name}acwing # 输出kevinacwing

使用变量时,需要在前面写 $符号。而加大括号 {}则是为了约束变量名称的范围,本例中,如果最后一行不加大括号,则会输出变量名为 nameacwing的值

只读变量

name=ykw
readonly name
declare -r name # 两种方法都可以定义只读变量
# 尝试修改时会报错:
-bash: name: readonly variable

删除变量

使用 unset可以删除变量

name=ykw
unset name
echo ${name} # 此时会输出空行

变量类型

  1. 自定义变量(局部变量

子进程不能访问的变量

  1. 环境变量(全局变量

子进程可以访问的变量

自定义->环境

name=ykw
export name # 第一种方法
declare -x name # 第二种方法

环境->自定义

export name=ykw
declare +x name # 改为自定义变量

字符串变量

可以用单引号、双引号,也可以不用引号。两种引号的区别:

  • 单引号内的内容会原样输出,不会执行、取变量
  • 双引号内的内容会进行执行、取变量
name=ykw
echo 'Hello, $name \"hh\"'
echo "Hello, $name \"hh\""
# 依次输出:
Hello, $name \"hh\"
Hello, ykw "hh" # 双引号中的`\"`会转义输出成`"`

获取字符串长度:

name=ykw
echo ${#name} # 输出结果为3

提取子串Substitution:

name="KevinYao"
echo ${name:0:5} # 下标从0开始。提取从0开始的5个字符。0表示起始位置,5表示子串长度

数组

一个数组中可以存放多个不同类型的值,只支持一维数组,初始化时不需要指明数组的大小

数组下标从0开始

定义和初始化

数组元素用小括号包裹,元素之间用空格隔开

array=(1 abc "def" kevin)

也可以直接定义某个元素的值,等价于上面的指令:

array[0]=1
array[1]=abc
array[2]="def"
array[3]=keivn

读取数组中某个元素的值

${array[index]} # 格式。其中index为某个元素的下标

例如:

array=(1 abc "def" yxc)
echo ${array[0]}
echo ${array[1]}
echo ${array[2]}
echo ${array[3]}

读取整个数组

${array[@]}
${array[*]} # 两种写法

例如:

array=(1 abc "def" yxc)

echo ${array[@]}  # 第一种写法
echo ${array[*]}  # 第二种写法
# 输出结果如下:
1 abc def yxc

数组长度

即读取整个数组:

${#array[@]}
${#array[*]}  # 两种写法

例如:

array=(1 abc "def" yxc)

echo ${#array[@]}  # 第一种写法
echo ${#array[*]}  # 第二种写法
# 输出结果如下:
4

注意和说明

可以稀疏地定义数组元素的下标,只占用相应的内存和长度:

array[0]=1
array[1]=abc
array[1000]=kevin
echo ${#array[*]}
# 输出结果如下:
3

expr指令

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

expr 表达式

输出内容

  • stdout中会输出结果。而对于逻辑表达式,如果结果为真,stdout为1;否则为0
  • exit code会返回状态值。对于一般表达式均为0。而对于逻辑表达式,如果结果为真,exit code为0;否则为1

整数表达式

进行算术操作时,会将参数转化成整数,如果转化失败会报错

  • 包含加减乘除取模运算
  • 小括号可以表示优先级
  • 小括号和乘号需要转义
a=3
b=4

echo `expr $a + $b` # 输出7
echo `expr $a - $b` # 输出-1
echo `expr $a \* $b` # 输出12,*需要转义
echo `expr $a / $b` # 输出0,整除
echo `expr $a % $b` # 输出3
echo `expr \( $a + 1 \) \* \( $b + 1 \)` # 输出20,值为(a + 1) * (b + 1)

注意要有空格否则会报错

字符串表达式

  • length STRING:返回 STRING的长度
  • index STRING TAR:在 STRING中依次查找 TAR的字符,返回第一个在 STRING中出现的字符的位置。下标从1开始
  • substr STRING POS LEN:返回一个子串。子串从 POS开始,长度为 LEN下标从1开始
str="Hello, World!"
echo `expr length "$str"` # 前后用飘号包裹,表示执行该命令。输出13
echo `expr index "$str" aWd` # 输出0
echo `expr substr "$str" 2 3` # 输出ell

逻辑表达式

  • |或运算

至少有一个为真,则为真;两个全为假,则为假。

如果第一个为真,则直接返回第一个参数的值,并且无需判断第二个。

如果第一个为假,则继续判断第二个,若为真,则返回第二个参数的值;若为假,则返回0(此时表达式为假

  • &与运算

两个全为真,则为真;至少有一个为假,则为假。

如果表达式为真,则返回第一个参数的值。

如果表达式为假,则返回0

同样有短路原则:如果第一个为假,则不会判断第二个

与运算或运算的返回值只能是参数值或者0

  • 比较大小的运算符 < <= = == != >= >

比较两端的参数。如果为真,则返回1,否则返回0

会先将参数转化成整数,如果失败,则按照字符串比较

其中 ===等价,由于 C/C++的习惯,我们更常用 ==

a=3
b=4

echo `expr $a \> $b`  # 输出0,>需要转义
echo `expr $a '<' $b`  # 输出1,也可以将特殊字符用引号引起来
echo `expr $a '>=' $b`  # 输出0
echo `expr $a \<\= $b`  # 输出1

c=0
d=5

echo `expr $c \& $d`  # 输出0。假,输出0
echo `expr $a \& $b`  # 输出3。真,输出第一个参数的值
echo `expr $c \| $d`  # 输出5。真,但第一个为0,所以输出第二个参数的值
echo `expr $a \| $b`  # 输出3。真,输出第一个参数的值

注意需要加空格

read指令

read用于从标准输入 stdin读取单行数据,可以有空格。可以类比为 scanf或者 gets()

参数说明

  • -p:可以呈现提示信息
  • -t:可以限制输入时间限制,即输入字符的等待时间,超过时间之后会跳过执行本条命令,单位为s
read name # 读取name的值
read -p "Please input your name: " -t 30 name # 读入name的值,等待时间30秒

echo命令

输出字符串

string=ykw
echo "My name is ${string}" # 变量格式
echo "Hello, World!" # 常量字符串
echo Hello, World # 引号可以省略

显示转义字符

只能使用双引号,单引号不显示转义字符

echo "\"Hello, World!\""
echo \"Hello, World!\" # 也可以省略双引号

输出换行

使用 \n进行换行,需要添加参数 -e

echo -e "Hello, World!\n"
# 除了转义字符换行之外,echo自带换行,会在输出最后进行换行。

输出不换行

使用 \c取消换行,同样需要参数 -e

echo -e "Hello, World!\c"

此时会取消 echo的自动换行

结果定向至文件

echo "Hello, World!" > output.txt # 将内容以覆盖的方式输出到output.txt中

不转义、取变量输出

name=ykw
echo '$name\"'
# 输出结果如下:
$name\"

输出命令结果

echo `date`
echo `ls`
# 输出该命令本来应该stdout输出的内容,等价于:
echo $(date)
echo $(ls)

printf命令

用于格式化输出,类似于 C/C++中的 printf,相比于 echo,不会在末尾自动换行

格式:

printf FORMAT [arguments]

示例:

printf "%10d.\n" 123  # 占10位,右对齐
printf "%-10.2f.\n" 123.123321  # 占10位,保留2位小数,左对齐
printf "My name is %s\n" "yxc"  # 格式化输出字符串
printf "%d * %d = %d\n"  2 3 `expr 2 \* 3` # 表达式的值作为参数

输出结果:

       123.
123.12    .
My name is yxc
2 * 3 = 6

test命令和判断符号[]

用于判断文件类型和比较变量

逻辑运算符

  • ||或运算
  • &&与运算

同样具有短路原则。表达式的 exit code为0则表示真,为非0则表示假。

test -e test.sh && echo "exist" || echo "Not exist"

分析:分成三个表达式。

如果文件 test.sh存在,test -e test.sh则为真。而后面的 echo "exist"必为真。所以如果文件存在,则输出 exist。此时前面的两个表达式的总值为真,根据 ||的短路原则,不用判断后面的 echo "Not exist"表达式

如果文件不存在,则 test表达式为假。而后面的 echo "exist"不用判断,因为 &&的短路原则说明其已经为假。而 ||虽然前面为假,仍然需要看第二个表达式,而第二个 echo必为真,且执行,输出 Not exist

[]的用法

  • []和 test的用法基本一致
  • 中括号内的每一项都要用空格隔开
  • 变量要用双引号包裹
name="Kevin Yao"
[ "$name" == "Kevin Yao" ] # 如果不用引号,由于变量中有空格,则会被判定为参数过多,相当于:
[ $name == "Kevin Yao" ] # 错误。

判断语句

可以类比 C/C++中的 if-else语句

格式:

if condition
then
    sen1
    sen2
    ...
fi

示例:

a=3
b=4

if [ "$a" -lt "$b" ] && [ "$a" -gt 2 ]
then
    echo ${a}在范围内
fi
# 输出结果:
3在范围内

单层if-else

if condition
then
    语句1
    语句2
    ...
else
    语句1
    语句2
    ...
fi
a=3
b=4

if ! [ "$a" -lt "$b" ]
then
    echo ${a}不小于${b}
else
    echo ${a}小于${b}
fi
# 输出结果:
3小于4

多层elif

a=4

if [ $a -eq 1 ]
then
    echo ${a}等于1
elif [ $a -eq 2 ]
then
    echo ${a}等于2
elif [ $a -eq 3 ]
then
    echo ${a}等于3
else
    echo 其他
fi

惊天大秘密

if后面一定是一个 condition

例如

if expr 3 '>' 3
then
    echo "hhh"
fi
# 输出内容:
0
# 不会输出hhh,因为if判断expr语句是假。

由于expr后面是逻辑语句,且为假,所以此时的expr会输出在 stdout一个0;而其返回值 exit code则为1

但是 if判断的就是这条命令,而不用取输出值。所以有错误写法:

if `expr 3 '>' 3 `
then 
    echo "hhh"
fi
# 此时会报错:
0: command not found

分析:由于expr前后加了飘号,导致其实际上是给了 if一个expr的 stdout进行判断。相当于:

if 0
then
    echo "hhh"
fi

由于 if后面不是一个命令,所以会报错:没有0这个命令

惊天大秘密expr命令有 stdout标准输出值,也有 exit code返回值。并且当为逻辑判断时,二者相反

实际上我们用 if语句时很少会进行这种判断,大部分都是使用 test[],所以惊天大秘密的分析讲解更多用于理解 stdout标注输出值和 exit code返回值

最后修改:2024 年 01 月 21 日
如果觉得我的文章对你有用,请随意赞赏