概论
shell属于一种解释性语言,不需要编译即可运行。由于是解释性语言,运行时的效率会比较低。
例如:递归方法计算从1到n的加和。
shell有很多种,例如 sh
,bash
,csh
,zsh
,ksh
等
sh
:Bourne Shell
bash
:Bourne 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} # 此时会输出空行
变量类型
- 自定义变量(局部变量
子进程不能访问的变量
- 环境变量(全局变量
子进程可以访问的变量
自定义->环境
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;否则为0exit 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
返回值