3 shell语法
3.1 概论
终端可以看做逐条执行的shell脚本,Linux默认使用bash,脚本文件第一行必须为
#! /bin/bash
可通过两种方式执行shell脚本
- 解释器执行:
bash xxx.sh
- 作为可执行文件执行
- 添加执行权限
chmod +x xxx.sh
- 执行
./xxx.sh
- 添加执行权限
3.2 注释
单行注释:类似python,用#
注释
多行注释:
:<<EOF
...
EOF
其中EOF
可替换成任意字符串,例如abc
3.3 变量
定义变量
定义不需要$
,而且=
两边不能有空格。字符串可用单引号、双引号或不用引号描述,例如下边三种变量声明都是正确的。
name1='abc'
name2="abc"
name3=abc
引用变量
需要用$
,可用{}
限定变量名边界,实现字符串与变量混合表示
echo ${name1}defg # 输出abcdefg
只读变量
声明前加上readonly
或declare -r
readonly name1
declare -r name2
删除变量
用unset
删除,此时变量为空串
unset name
echo name # 输出空串和一个换行符
变量类型
根据使用范围,可分为全局变量和局部变量。
全局变量又称环境变量,其它子进程(其它bash终端)可访问,可用以下两种方式声明:
export name
declare -x name
局部变量又称自定义变量,只有当前进程能访问,可通过declare +x
把全局变量转成局部变量
declare +x name
单引号字符串与双引号字符串的区别
单引号的内容原样输出,不支持变量引用嵌套$name
;而双引号的内容中可以嵌套变量引用,不加引号与双引号效果一致。
name=abc
echo '$name defg' # 输出$name defg
echo "$name defg" # 输出abc defg
获取字符串的长度
name="abc"
echo ${#name} # 输出3
提取子串F’x
第1个数为起始地址(从0开始),第2个数为长度
name="abcdefg"
echo ${name:0:5} # 输出abcde
3.4 默认变量
$0
表示文件路径,例如./xxx.sh
,$1
、$2
等依次表示脚本的第1个参数、第2个参数。
参数 | 说明 |
---|---|
$# |
参数个数 |
$* |
等价于"$1 $2 $3 ..." |
$@ |
等价于"$1" "$2" "$3" ..." |
$$ |
当前进程ID |
$? |
上一条命令的退出状态,例如0表示正常退出,其它为异常退出 |
$(command) |
返回command 的标准输出stdout,可嵌套 |
`command` | 返回command 的标准输出stdout,不可嵌套 |
3.4 数组
只支持一维数组,但数组元素类型可以不同,而且不需要指名数组大小,下标从0开始。数组用小括号()
表示,元素用空格隔开
array=(1 abc 'defg') # 整个定义
array[3] = "hijk" # 逐个定义
用$
读取数组元素,可用@
或*
表示整个数组
echo $(array[0]) # 输出索引为0的元素
echo $(array[@]) # 输出所有元素
echo $(array[*]) # 输出所有元素
读取数组长度,有如下两种方式,返回的都是数组实际长度
echo $(#array[@])
echo $(#array[*])
3.5 expr命令
可用于表达式求值,格式为echo 表达式
。表达式用空格隔开每一项,个别符号需要用\
转义,例如乘号*
。如果表达式包含空格以及其它特殊字符,需要用引号括起。
表达式同时在stdout
和exit code
输出结果。如果表达式是逻辑表达式,且为真时,则stdout
返回1
,exit code
返回0
。如果表达式是非逻辑表达式,则一律返回0
。
3.5.1 字符串表达式
可用length str
求str
的长度
可用index string charset
求charset
中任意单个字符首次在string
中出现的位置,下标从1开始
可用substr string position length
求string
从position
起,长度为length
的子串
str="HelloWorld"
echo `expr length "$str"`
echo `expr index "$str" abc`
echo `expr substr "$str" 2 3`
3.5.2 整数表达式
加+
减-
乘\*
除/
取余%
括号\(
\)
,注意乘号和括号需要用反斜杠\
转义。
echo `expr \( $a + $b \) \* $c` # 输出(a+b)*c
3.5.3 逻辑表达式
|
:如果左边参数非空且非0,则返回左边参数;否则如果右边参数非空且非0,返回右边参数;都不满足返回0。
&
:如果左边参数和右边参数非空且非0,则返回左边参数;否则返回0。
|
和&
都具有短路特性,如果计算左边参数足以确定返回值,则不会计算右边参数。
<
、<=
、>
、>=
、==
、!=
都与python一致,除了=
也表示相等关系,即与==
同义
3.6 read命令
read
类似cin
,后边接变量名,例如read name
read
有两个参数-p
,接提示信息;-t
,接超时时间,单位为秒,超过时间则忽略该命令
read -p "What's your name?" -t 30 name
3.7 echo命令
echo
主要用于输出字符串,字符串可不加引号表示,也可加单引号表示原生字符串,或加双引号实现嵌套变量的字符串
双引号需要用\
转义,单引号不需要
参数-e
可开启转义,从而能在字符串使用转义字符\n
echo -e "Hi\n" # 额外多输出一个换行符
若想取消echo
默认输出的换行符,可用\c
echo -e "Hi\c"
重定向
echo "Hello world" > output.txt
显示执行结果
echo `ls`
3.8 printf命令
类似C++
的printf
,其格式为
printf format-string [arguments...]
printf "%10d.\n" 100
3.9 test命令与判断符号[]
3.9.1 test用法
test
命令常用于判断文件类型,以及比较变量,它的返回结果是exit code
,需要用$?
读取。输入man test
可查看test
的用法。
exit code
中,0
表示真,其它为假。
逻辑运算符&&
和||
都具有短路原则,只是这是真的逻辑表达式,返回逻辑值1
和0
,而不像&
和|
只执行,不返回exit code
。
test 2 -lt 3
echo $? # 逻辑表达式为真,因此exit code为0
3.9.2 文件类型判断
常用参数有
-e
:文件是否存在-f
:是否为文件-d
:是否为目录
为了提高代码可读性,常常这样编写代码判断文件是否存在:
test -e filename && echo "Exist" || echo "Not exist"
3.9.3 文件权限判断
常用参数有
-r
:文件是否可读-w
:文件是否可写-x
:文件是否可执行-s
:文件是否为空文件
3.9.4 整数间比较
类似Latex,test
命令的比较符号有-eq
、-ne
、-gt
、-lt
、-ge
、-le
3.9.5 字符串比较
常用参数有
-z
:字符串是否为空,如果为空返回True
-n
:字符串是否非空,如果非空返回True
==
:判断字符串是否相同!=
:判断字符串是否不同
3.9.6 多重条件判定
常用参数有:与-a
、或-o
、非!
test ! -x file # 文件不可执行时返回True
3.9.7 判断符号[]
[]
用法几乎与test
一样,可看做简化版的test
,常用于if
语句中。注意中括号前后的空格,变量和常量尽量用双引号括起。
test 2 -lt 3
[ 2 -lt 3 ] # 与上一条语句等价
[ "$name" == "abc" ] # 用双引号括起
3.10 判断语句
3.10.1 if语句
bash
中的if
语句类似C++中的if
语句块,其语法格式如下
if condition
then
statement
elif condition
then
statement
else
statement
fi
其中condition
可用expr
逻辑表达式或test
表达式,例如[ "$a" -eq 1 ]
表示变量a是否等于1,满足则执行then
后边的语句。
3.10.1 case语句
bash
中的case
语句类似C++中switch
语句块,其语法格式如下
case $Name in
value1)
statement
;;
value2)
statement
;;
*)
statement
;;
esac
例子
case $a in
1)
echo "a = 1"
;;
2)
echo "a = 2"
;;
*)
echo "a = other"
;;
esac
其中;;
类似break
3.11 循环语句
3.11.1 for语句
(1)for...in
遍历指定值
for var in val1 val2 val3
do
statement
done
遍历数组
for var in `ls`
do
statement
done
把ls
的stdout
作为数组进行遍历
可用$(seq 1 10)
或{1..10}
表示数组1 2 ... 10
,也可用{a..z}
表示数组a b ... z
(2)for((expression; condition; expression))
类似C++
中的for
语句块,只是需要用双括号括起,此时允许括号内的=
号两侧用空格隔开(通常不允许)
例如:
for ((i = 1; i <= 10; i++))
do
echo $i
done
3.11.2 while语句
bash
中的while
与C++
类似,语法结构如下
while condition
do
statement
done
例子:
while read name
do
echo $name
done
若要终止读取,可按ctrl
+d
,或直接结束程序ctrl
+c
。
3.11.3 until语句
until
语句与while
相反,当condition
为假时才进入循环,为真是结束循环,其语法结构如下:
until condition
do
statement
done
例子
until [ "$(word)" == "yes" ] || [ "$(word)" == "YES" ]
do
read -p "Please input yes/YES to stop this program: " word
done
3.11.4 循环控制
类似C++
,bash
也有continue
和break
语句,功能一样,只是bash
中的break
语句不能用于case
语句块中。
如果遇到死循环,可用top
找到进程PID,然后用命令kill -9 PID
结束进程。
3.12 函数
bash
中的函数类似C++
中的函数,但return
不同。在bash
中,return
只能返回0
~255
之间的exit code
,若想获取返回值,可用echo
输出到stdout
中,然后再通过$(function_name)
获取其stdout
结果。exit code
可通过$?
获取。
函数语法结构
[function] func_name() {
statement
}
其中function关键字可省略。
当不写return
时,函数默认返回0
函数参数
$1
表示函数的第一个输入参数,以此类推。但$0
不表示函数名,而是文件名。
局部变量
用local
关键字声明,范围为函数体内。
func() {
local name=abc
echo $name
}
3.13 exit命令
exit
用于结束当前进程,并返回一个退出状态,状态值为0~255之间的整数,其中0表示成功,其余表示失败。
3.14 文件重定向
每个进程默认打开3个文件描述符
- 标准输入
stdin
:从命令行读取数据,文件描述符为0
- 标准输出
stdout
:向命令行输出数据,文件描述符为1
- 标准错误输出
stderr
:向命令行输出错误,文件描述符为2
重定向语法
命令 | 说明 |
---|---|
command > file |
将stdout 重定向到file 中,即把command 的结果输出到file 中,会覆盖file 中的内容 |
command < file |
将stdin 重定向到file 中,即command 从file 中读入数据 |
command >> file |
与> 类似,只是是以追加的方式输出到file 中,不会覆盖file 的原始内容 |
command n> file |
将文件描述符n 重定向到file 中 |
command n>> file |
将文件描述符n 以追加的方式重定向到file 中 |
例子
./test.sh < input.txt > output.txt # test.sh从input.txt读取数据,结果输出到output.txt中
3.15 引入外部脚本
类似C++
引入头文件,有如下两种方式引用
. filename # 注意空格
source filename
3.16 作业核心代码
(1)task1
#! /bin/bash
root=/home/acs/homework/lesson_1
# ------------------homework0-------------------
homework 1 create 0
current="${root}/homework_0"
for i in a b c
do
tmp="${current}/dir_${i}"
mkdir $tmp
done
# ------------------homework1-------------------
homework 1 create 1
current="${root}/homework_1"
for i in a b c
do
tmp=${current}/${i}.txt
cp ${tmp} ${tmp}.bak
done
# ------------------homework2-------------------
homework 1 create 2
current="${root}/homework_2"
for i in a b c
do
tmp=${current}/${i}
mv ${tmp}.txt ${tmp}_new.txt
done
# ------------------homework3-------------------
homework 1 create 3
current="${root}/homework_3"
for i in a b c
do
tmp=${current}/${i}.txt
mv ${current}/dir_a/${i}.txt ${current}/dir_b/${i}.txt
done
# ------------------homework4-------------------
homework 1 create 4
current="${root}/homework_4"
for i in a b c
do
tmp=${current}/${i}.txt
rm ${tmp}
done
# ------------------homework5-------------------
homework 1 create 5
current="${root}/homework_5"
for i in a b c
do
tmp=${current}/dir_${i}
rm -r ${tmp}
done
# ------------------homework6-------------------
homework 1 create 6
current="${root}/homework_6"
mkdir ${current}/dir_a
mv ${current}/task.txt ${current}/dir_a/done.txt
# ------------------homework7-------------------
homework 1 create 7
current="${root}/homework_7"
for i in {0..2}
do
mkdir ${current}/dir_${i}
for j in a b c
do
cp ${current}/${j}.txt ${current}/dir_${i}/${j}${i}.txt
done
done
# ------------------homework8-------------------
homework 1 create 8
current="${root}/homework_8"
rm ${current}/dir_a/a.txt
mv ${current}/dir_b/b.txt ${current}/dir_b/b_new.txt
cp ${current}/dir_c/c.txt ${current}/dir_c/c.txt.bak
# ------------------homework9-------------------
homework 1 create 9
current="${root}/homework_9"
rm ${current}/*.txt
homework 1 test
(2)task2
#! /bin/bash
if [ $# -ne 1 ]
then
echo "arguments not valid"
exit 1
fi
if [ ! -e "$1" ]
then
echo "not exist"
exit 2
fi
if [ -f "$1" ]
then
echo "regular file"
fi
if [ -d "$1" ]
then
echo "directory"
fi
if [ -r "$1" ]
then
echo "readable"
fi
if [ -w "$1" ]
then
echo "writable"
fi
if [ -x "$1" ]
then
echo "executable"
fi
(3)task3
#! /bin/bash
read n
a=1
b=1
c=1
for(( i = 2; i <= n; i++))
do
c=`expr $a + $b`
a=$b
b=$c
done
echo $c
(4)task4
#! /bin/bash
read n
read m
for (( i = 1; i <= n; i++))
do
st[$i]=0
done
dfs() {
if [ $1 -eq $n ]
then
m=`expr $m - 1`
if [ $m -eq 0 ]
then
echo ${path[@]}
return 0
fi
return 1
fi
local j=0
for(( j = 1; j <= n; j++))
do
if [ ${st[$j]} -eq 0 ]
then
st[$j]=1
path[$1]=$j
if dfs `expr $1 + 1`
then
return 0
fi
st[$j]=0
fi
done
return 1
}
dfs 0
(5)task5
#! /bin/bash
read n < "$1"
res=0
for(( i = 1; i <= n; i++))
do
res=`expr $res + $i \* $i`
done
echo $res > "$2"